diff --git a/III. Checksec/custom/aslr_test.c b/III. Checksec/custom/aslr_test.c new file mode 100644 index 0000000..3423d6e --- /dev/null +++ b/III. Checksec/custom/aslr_test.c @@ -0,0 +1,7 @@ +#include + +int main(){ + printf("main address : %p\n",&main); + printf("printf address : %p\n",&printf); + return 0; +} \ No newline at end of file diff --git a/III. Checksec/custom/buffer_overflow.c b/III. Checksec/custom/buffer_overflow.c new file mode 100644 index 0000000..683b696 --- /dev/null +++ b/III. Checksec/custom/buffer_overflow.c @@ -0,0 +1,9 @@ +#include +#include + +int main() { + char buffer[10]; + strcpy(buffer, "Hello, World!"); // Vulnerable operation + printf("You entered: %s\n", buffer); + return 0; +} \ No newline at end of file diff --git a/III. Checksec/custom/example.m b/III. Checksec/custom/example.m new file mode 100644 index 0000000..5a74453 --- /dev/null +++ b/III. Checksec/custom/example.m @@ -0,0 +1,20 @@ +#import + +@interface Person : NSObject +@property NSString *name; +@end + +@implementation Person +@end + +int main(int argc, const char * argv[]) { + @autoreleasepool { + // Create a Person object + Person *person = [[Person alloc] init]; + person.name = @"John Doe"; + + // The person object will be automatically managed by ARC + NSLog(@"Person's name: %@", person.name); + } + return 0; +} \ No newline at end of file diff --git a/III. Checksec/custom/heap_example.c b/III. Checksec/custom/heap_example.c new file mode 100644 index 0000000..7ff8559 --- /dev/null +++ b/III. Checksec/custom/heap_example.c @@ -0,0 +1,23 @@ +#include +#include + +int main() { + // Allocate memory on the heap + char *heapMemory = (char *)malloc(100 * sizeof(char)); + + if (heapMemory == NULL) { + fprintf(stderr, "Memory allocation failed.\n"); + return 1; + } + + printf("Memory allocated. Press Enter to exit.\n"); + + // Wait for Enter key press + while (getchar() != '\n'); + + // Free allocated memory + free(heapMemory); + + return 0; +} + diff --git a/III. Checksec/custom/hello.c b/III. Checksec/custom/hello.c new file mode 100644 index 0000000..8321e25 --- /dev/null +++ b/III. Checksec/custom/hello.c @@ -0,0 +1,6 @@ +#include + +int main() { + printf("Hello, World!\n"); + return 0; +} \ No newline at end of file diff --git a/III. Checksec/mac/Header.cpp b/III. Checksec/mac/Header.cpp new file mode 100644 index 0000000..fcd6caa --- /dev/null +++ b/III. Checksec/mac/Header.cpp @@ -0,0 +1,2537 @@ +// Source: https://github.com/apple-oss-distributions/dyld/blob/main/mach_o/Header.cpp#L1519 +/* + * Copyright (c) 2021 Apple Inc. All rights reserved. + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +#include + +#include +#include +#include +#include + +#include +#if !TARGET_OS_EXCLAVEKIT + #include +#endif + +#include "Array.h" +#include "Header.h" +#include "Architecture.h" +#include "Misc.h" +#include "Policy.h" +#include "LoggingStub.h" + +using mach_o::Architecture; +using mach_o::Platform; +using mach_o::PlatformAndVersions; +using mach_o::Policy; + +namespace mach_o { + +// +// MARK: --- methods that read mach_header --- +// + +bool Header::hasMachOMagic() const +{ + return ((mh.magic == MH_MAGIC) || (mh.magic == MH_MAGIC_64)); +} + +bool Header::hasMachOBigEndianMagic() const +{ + return ((mh.magic == MH_CIGAM) || (mh.magic == MH_CIGAM_64)); +} + +bool Header::is64() const +{ + return (mh.magic == MH_MAGIC_64); +} + +uint32_t Header::machHeaderSize() const +{ + return is64() ? sizeof(mach_header_64) : sizeof(mach_header); +} + +uint32_t Header::pointerSize() const +{ + if ( mh.magic == MH_MAGIC_64 ) + return 8; + else + return 4; +} + +bool Header::uses16KPages() const +{ + switch ( mh.cputype ) { + case CPU_TYPE_ARM64: + case CPU_TYPE_ARM64_32: + return true; + case CPU_TYPE_ARM: + // iOS is 16k aligned for armv7/armv7s and watchOS armv7k is 16k aligned + return mh.cpusubtype == CPU_SUBTYPE_ARM_V7K; + default: + return false; + } +} + +bool Header::isArch(const char* aName) const +{ + return (strcmp(aName, this->archName()) == 0); +} + +const char* Header::archName() const +{ + return Architecture(&mh).name(); +} + +Architecture Header::arch() const +{ + return Architecture(&mh); +} + +bool Header::inDyldCache() const +{ + return (mh.flags & MH_DYLIB_IN_CACHE); +} + +bool Header::isDyldManaged() const +{ + switch ( mh.filetype ) { + case MH_BUNDLE: + case MH_EXECUTE: + case MH_DYLIB: + return ((mh.flags & MH_DYLDLINK) != 0); + default: + break; + } + return false; +} + +bool Header::isDylib() const +{ + return (mh.filetype == MH_DYLIB); +} + +bool Header::isBundle() const +{ + return (mh.filetype == MH_BUNDLE); +} + +bool Header::isMainExecutable() const +{ + return (mh.filetype == MH_EXECUTE); +} + +bool Header::isDynamicExecutable() const +{ + if ( mh.filetype != MH_EXECUTE ) + return false; + + // static executables do not have dyld load command + return hasLoadCommand(LC_LOAD_DYLINKER); +} + +bool Header::isKextBundle() const +{ + return (mh.filetype == MH_KEXT_BUNDLE); +} + +bool Header::isObjectFile() const +{ + return (mh.filetype == MH_OBJECT); +} + +bool Header::isFileSet() const +{ + return (mh.filetype == MH_FILESET); +} + +bool Header::isPIE() const +{ + return (mh.flags & MH_PIE); +} + +bool Header::isPreload() const +{ + return (mh.filetype == MH_PRELOAD); +} + +bool Header::hasWeakDefs() const +{ + return (mh.flags & MH_WEAK_DEFINES); +} + +bool Header::usesWeakDefs() const +{ + return (mh.flags & MH_BINDS_TO_WEAK); +} + +bool Header::hasThreadLocalVariables() const +{ + return (mh.flags & MH_HAS_TLV_DESCRIPTORS); +} + +const Header* Header::isMachO(std::span content) +{ + if ( content.size() < sizeof(mach_header) ) + return nullptr; + + const Header* mh = (const Header*)content.data(); + if ( mh->hasMachOMagic() ) + return mh; + return nullptr; +} + +bool Header::mayHaveTextFixups() const +{ + // only i386 binaries support text fixups + if ( mh.cputype == CPU_TYPE_I386 ) + return true; + // and x86_64 kext bundles + if ( isKextBundle() && (mh.cputype == CPU_TYPE_X86_64) ) + return true; + return false; +} + +bool Header::hasSubsectionsViaSymbols() const +{ + return (this->mh.flags & MH_SUBSECTIONS_VIA_SYMBOLS) != 0; +} + +bool Header::noReexportedDylibs() const +{ + return (this->mh.flags & MH_NO_REEXPORTED_DYLIBS) != 0; +} + +bool Header::isAppExtensionSafe() const +{ + return (this->mh.flags & MH_APP_EXTENSION_SAFE) != 0; +} + +bool Header::isSimSupport() const +{ + return (this->mh.flags & MH_SIM_SUPPORT) != 0; +} + + +// +// MARK: --- methods for validating mach-o content --- +// + +PlatformAndVersions Header::platformAndVersions() const +{ + // should be one platform load command (exception is zippered dylibs) + __block PlatformAndVersions pvs; + forEachPlatformLoadCommand(^(Platform platform, Version32 minOS, Version32 sdk) { + Error err = pvs.zip({ platform, minOS, sdk }); + assert(err.noError()); + }); + return pvs; +} + +Error Header::validSemanticsPlatform() const +{ + // should be one platform load command (exception is zippered dylibs) + __block PlatformAndVersions pvs; + __block Error badPlatform; + forEachPlatformLoadCommand(^(Platform platform, Version32 minOS, Version32 sdk) { + if ( badPlatform.hasError() ) return; + + if ( Error err = platform.valid() ) { + badPlatform = std::move(err); + return; + } + badPlatform = pvs.zip({ platform, minOS, sdk }); + }); + if ( badPlatform ) + return std::move(badPlatform); + +#if BUILDING_MACHO_WRITER + if ( pvs.platform.empty() ) + return Error::none(); // allow empty platform in static linker +#endif + + return pvs.platform.valid(); +} + +Error Header::valid(uint64_t fileSize) const +{ + if ( fileSize < sizeof(mach_header) ) + return Error("file is too short"); + + if ( !hasMachOMagic() ) + return Error("not a mach-o file (start is no MH_MAGIC[_64])"); + + if ( Error err = validStructureLoadCommands(fileSize) ) + return err; + + if ( Error err = validSemanticsPlatform() ) + return err; + + // create policy object + Policy policy(arch(), platformAndVersions(), mh.filetype, false); + + if ( Error err = validSemanticsUUID(policy) ) + return err; + + if ( Error err = validSemanticsInstallName(policy) ) + return err; + + if ( Error err = validSemanticsDependents(policy) ) + return err; + + if ( Error err = validSemanticsRPath(policy) ) + return err; + + if ( Error err = validSemanticsSegments(policy, fileSize) ) + return err; + + if ( Error err = validSemanticsLinkerOptions(policy) ) + return err; + + if ( isMainExecutable() ) { + if ( Error err = validSemanticsMain(policy) ) + return err; + } + + return Error::none(); +} + +static Error stringOverflow(const load_command* cmd, uint32_t index, uint32_t strOffset) +{ + if ( strOffset >= cmd->cmdsize ) + return Error("load command #%d string offset (%u) outside its size (%u)", index, strOffset, cmd->cmdsize); + + const char* str = (char*)cmd + strOffset; + const char* end = (char*)cmd + cmd->cmdsize; + for ( const char* s = str; s < end; ++s ) { + if ( *s == '\0' ) { + return Error::none(); + } + } + return Error("load command #%d string extends beyond end of load command", index); +} + +Error Header::validStructureLoadCommands(uint64_t fileSize) const +{ + // check load command don't exceed file length + const uint64_t headerAndLCSize = mh.sizeofcmds + machHeaderSize(); + if ( headerAndLCSize > fileSize ) { + return Error("load commands length (%llu) exceeds length of file (%llu)", headerAndLCSize, fileSize); + } + + // check for reconized filetype + switch ( mh.filetype ) { + case MH_EXECUTE: + case MH_DYLIB: + case MH_DYLINKER: + case MH_BUNDLE: + case MH_KEXT_BUNDLE: + case MH_FILESET: + case MH_PRELOAD: + case MH_OBJECT: + break; + default: + return Error("unknown filetype %d", mh.filetype); + } + + // walk all load commands and sanity check them + __block int index = 1; + __block Error lcError; + auto lcChecker = ^(const load_command* cmd, bool& stop) { + const dylib_command* dylibCmd; + const rpath_command* rpathCmd; + const sub_umbrella_command* umbrellaCmd; + const sub_client_command* clientCmd; + const sub_library_command* libraryCmd; + const build_version_command* buildVersCmd; + const segment_command* segCmd; + const segment_command_64* seg64Cmd; + const fileset_entry_command* fileSetCmd; + switch ( cmd->cmd ) { + case LC_ID_DYLIB: + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + dylibCmd = (dylib_command*)cmd; + lcError = stringOverflow(cmd, index, dylibCmd->dylib.name.offset); + break; + case LC_RPATH: + rpathCmd = (rpath_command*)cmd; + lcError = stringOverflow(cmd, index, rpathCmd->path.offset); + break; + case LC_SUB_UMBRELLA: + umbrellaCmd = (sub_umbrella_command*)cmd; + lcError = stringOverflow(cmd, index, umbrellaCmd->sub_umbrella.offset); + break; + case LC_SUB_CLIENT: + clientCmd = (sub_client_command*)cmd; + lcError = stringOverflow(cmd, index, clientCmd->client.offset); + break; + case LC_SUB_LIBRARY: + libraryCmd = (sub_library_command*)cmd; + lcError = stringOverflow(cmd, index, libraryCmd->sub_library.offset); + break; + case LC_SYMTAB: + if ( cmd->cmdsize != sizeof(symtab_command) ) + lcError = Error("load command #%d LC_SYMTAB size wrong", index); + break; + case LC_DYSYMTAB: + if ( cmd->cmdsize != sizeof(dysymtab_command) ) + lcError = Error("load command #%d LC_DYSYMTAB size wrong", index); + break; + case LC_SEGMENT_SPLIT_INFO: + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + lcError = Error("load command #%d LC_SEGMENT_SPLIT_INFO size wrong", index); + break; + case LC_ATOM_INFO: + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + lcError = Error("load command #%d LC_ATOM_INFO size wrong", index); + break; + case LC_FUNCTION_STARTS: + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + lcError = Error("load command #%d LC_FUNCTION_STARTS size wrong", index); + break; + case LC_DYLD_EXPORTS_TRIE: + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + lcError = Error("load command #%d LC_DYLD_EXPORTS_TRIE size wrong", index); + break; + case LC_DYLD_CHAINED_FIXUPS: + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + lcError = Error("load command #%d LC_DYLD_CHAINED_FIXUPS size wrong", index); + break; + case LC_ENCRYPTION_INFO: + if ( cmd->cmdsize != sizeof(encryption_info_command) ) + lcError = Error("load command #%d LC_ENCRYPTION_INFO size wrong", index); + break; + case LC_ENCRYPTION_INFO_64: + if ( cmd->cmdsize != sizeof(encryption_info_command_64) ) + lcError = Error("load command #%d LC_ENCRYPTION_INFO_64 size wrong", index); + break; + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + if ( cmd->cmdsize != sizeof(dyld_info_command) ) + lcError = Error("load command #%d LC_DYLD_INFO_ONLY size wrong", index); + break; + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: + if ( cmd->cmdsize != sizeof(version_min_command) ) + lcError = Error("load command #%d LC_VERSION_MIN_* size wrong", index); + break; + case LC_UUID: + if ( cmd->cmdsize != sizeof(uuid_command) ) + lcError = Error("load command #%d LC_UUID size wrong", index); + break; + case LC_BUILD_VERSION: + buildVersCmd = (build_version_command*)cmd; + if ( cmd->cmdsize != (sizeof(build_version_command) + buildVersCmd->ntools * sizeof(build_tool_version)) ) + lcError = Error("load command #%d LC_BUILD_VERSION size wrong", index); + break; + case LC_MAIN: + if ( cmd->cmdsize != sizeof(entry_point_command) ) + lcError = Error("load command #%d LC_MAIN size wrong", index); + break; + case LC_SEGMENT: + segCmd = (segment_command*)cmd; + if ( cmd->cmdsize != (sizeof(segment_command) + segCmd->nsects * sizeof(section)) ) + lcError = Error("load command #%d LC_SEGMENT size does not match number of sections", index); + break; + case LC_SEGMENT_64: + seg64Cmd = (segment_command_64*)cmd; + if ( cmd->cmdsize != (sizeof(segment_command_64) + seg64Cmd->nsects * sizeof(section_64)) ) + lcError = Error("load command #%d LC_SEGMENT_64 size does not match number of sections", index); + break; + case LC_FILESET_ENTRY: + fileSetCmd = (fileset_entry_command*)cmd; + lcError = stringOverflow(cmd, index, fileSetCmd->entry_id.offset); + break; + default: + if ( cmd->cmd & LC_REQ_DYLD ) + lcError = Error("load command #%d unknown required load command 0x%08X", index, cmd->cmd); + break; + } + ++index; + if ( lcError ) + stop = true; + }; + if ( Error err = this->forEachLoadCommand(lcChecker) ) + return err; + if ( lcError ) + return std::move(lcError); + /* + // check load commands fit in TEXT segment + if ( this->isDyldManaged() ) { + __block bool foundTEXT = false; + __block Error segError; + forEachSegment(^(const SegmentInfo& segInfo, bool& stop) { + if ( strcmp(segInfo.segName, "__TEXT") == 0 ) { + foundTEXT = true; + if ( headerAndLCSize > segInfo.fileSize ) { + segError = Error("load commands (%llu) exceed length of __TEXT segment (%llu)", headerAndLCSize, segInfo.fileSize); + } + if ( segInfo.fileOffset != 0 ) { + segError = Error("__TEXT segment not start of mach-o (%llu)", segInfo.fileOffset); + } + stop = true; + } + }); + if ( segError ) + return std::move(segError); + if ( !foundTEXT ) { + return Error("missing __TEXT segment"); + } + } +*/ + return Error::none(); +} + + +Error Header::validSemanticsUUID(const Policy& policy) const +{ + // should have at most one LC_UUID + __block unsigned uuidCount = 0; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_UUID ) + ++uuidCount; + }); + if ( uuidCount > 1 ) + return Error("too many LC_UUID load commands"); + if ( (uuidCount == 0) && policy.enforceHasUUID() ) + return Error("missing LC_UUID load command"); + + return Error::none(); +} + +Error Header::validSemanticsInstallName(const Policy& policy) const +{ + __block const char* installName = nullptr; + __block int foundCount = 0; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_ID_DYLIB ) { + const dylib_command* dylibCmd = (dylib_command*)cmd; + installName = (char*)dylibCmd + dylibCmd->dylib.name.offset; + ++foundCount; + } + }); + if ( foundCount > 1 ) + return Error("multiple LC_ID_DYLIB found"); + + if ( this->isDylib() ) { + if ( installName == nullptr ) + return Error("MH_DYLIB is missing LC_ID_DYLIB"); +#if 0 // FIXME: need path plumbed down + if ( policy.enforceInstallNamesAreRealPaths() ) { + // new binary, so check that part after @xpath/ is real (not symlinks) + if ( (strncmp(installName, "@loader_path/", 13) == 0) || (strncmp(installName, "@executable_path/", 17) == 0) ) { + if ( const char* s = strchr(installName, '/') ) { + while (strncmp(s, "/..", 3) == 0) + s += 3; + const char* trailingInstallPath = s; + const char* trailingRealPath = &path[strlen(path)-strlen(trailingInstallPath)]; + if ( strcmp(trailingRealPath, trailingInstallPath) != 0 ) { + Error("install name '%s' contains symlinks", installName); + } + } + } + } +#endif + } + else { + if ( installName != nullptr ) + return Error("found LC_ID_DYLIB found in non-MH_DYLIB"); + } + + return Error::none(); +} + +Error Header::validSemanticsDependents(const Policy& policy) const +{ + // gather info + __block Error dupDepError; + __block int depCount = 0; + const char* depPathsBuffer[256]; + const char** depPaths = depPathsBuffer; + const bool enforceNoDupDylibs = policy.enforceNoDuplicateDylibs(); + const bool hasWarningHandler = mach_o::hasWarningHandler(); + // don't use forEachDependentDylib, because it synthesizes libSystem.dylib + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + switch ( cmd->cmd ) { + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: { + const dylib_command* dylibCmd = (dylib_command*)cmd; + const char* loadPath = (char*)dylibCmd + dylibCmd->dylib.name.offset; + if ( (depCount < 256) && ( enforceNoDupDylibs || hasWarningHandler ) ) { + for ( int i = 0; i < depCount; ++i ) { + if ( strcmp(loadPath, depPaths[i]) == 0 ) { + if ( enforceNoDupDylibs ) { + dupDepError = Error("duplicate dependent dylib '%s'", loadPath); + stop = true; + } else + warning(this, "duplicate dependent dylib are deprecated ('%s')", loadPath); + } + } + depPaths[depCount] = loadPath; + } + ++depCount; + } break; + } + }); + if ( dupDepError ) + return std::move(dupDepError); + + // all new binaries must link with something + if ( this->isDyldManaged() && policy.enforceHasLinkedDylibs() && (depCount == 0) ) { + // except for dylibs in libSystem.dylib which are ok to link with nothing (they are on bottom) + const char* libSystemDir = this->builtForPlatform(Platform::driverKit, true) ? "/System/DriverKit/usr/lib/system/" : "/usr/lib/system/"; + const char* installName = this->installName(); + bool isNotLibSystem = (installName == nullptr) || (strncmp(installName, libSystemDir, strlen(libSystemDir)) != 0); + if ( isNotLibSystem ) + return Error("missing LC_LOAD_DYLIB (must link with at least libSystem.dylib)"); + } + return Error::none(); +} + +Error Header::validSemanticsRPath(const Policy& policy) const +{ + const bool enforceNoDupRPath = policy.enforceNoDuplicateRPaths(); + if ( !enforceNoDupRPath && !hasWarningHandler() ) + return Error::none(); + + __block Error dupRPathError; + __block int rpathCount = 0; + const char* rpathsBuffer[64]; + const char** rpaths = rpathsBuffer; + forEachRPath(^(const char* rPath, bool& stop) { + if ( rpathCount < 64 ) { + for ( int i = 0; i < rpathCount; ++i ) { + if ( strcmp(rPath, rpaths[i]) == 0 ) { + if ( enforceNoDupRPath ) { + dupRPathError = Error("duplicate LC_RPATH '%s'", rPath); + stop = true; + } else + warning(this, "duplicate LC_RPATH are deprecated ('%s')", rPath); + } + } + rpaths[rpathCount] = rPath; + } + ++rpathCount; + }); + return std::move(dupRPathError); +} + +#if !TARGET_OS_EXCLAVEKIT +template +Error Header::validSegment(const Policy& policy, uint64_t wholeFileSize, const SG* seg) const +{ + if ( greaterThanAddOrOverflow(seg->fileoff, seg->filesize, wholeFileSize) ) + return Error("segment '%s' load command content extends beyond end of file", seg->segname); + + // dyld should support non-allocatable __LLVM segment + if ( !isObjectFile() ) { + if ( (seg->filesize > seg->vmsize) && ((seg->vmsize != 0) || ((seg->flags & SG_NORELOC) == 0)) ) + return Error("segment '%s' filesize exceeds vmsize", seg->segname); + } + + // check permission bits + if ( (seg->initprot & 0xFFFFFFF8) != 0 ) { + return Error("%s segment permissions has invalid bits set (0x%08X)", seg->segname, seg->initprot); + } + if ( policy.enforceTextSegmentPermissions() ) { + if ( (strcmp(seg->segname, "__TEXT") == 0) && (seg->initprot != (VM_PROT_READ | VM_PROT_EXECUTE)) ) + return Error("__TEXT segment permissions is not 'r-x'"); + } + if ( policy.enforceReadOnlyLinkedit() ) { + if ( (strcmp(seg->segname, "__LINKEDIT") == 0) && (seg->initprot != VM_PROT_READ) ) + return Error("__LINKEDIT segment permissions is not 'r--'"); + } + if ( policy.enforceDataSegmentPermissions() ) { + if ( (strcmp(seg->segname, "__DATA") == 0) && (seg->initprot != (VM_PROT_READ | VM_PROT_WRITE)) ) + return Error("__DATA segment permissions is not 'rw-'"); + if ( strcmp(seg->segname, "__DATA_CONST") == 0 ) { + if ( seg->initprot != (VM_PROT_READ | VM_PROT_WRITE) ) + return Error("__DATA_CONST segment permissions is not 'rw-'"); + if ( (seg->flags & SG_READ_ONLY) == 0 ) { + if ( this->isDylib() && this->hasSplitSegInfo() ) { + // dylibs in dyld cache are allowed to not have SG_READ_ONLY set + } + else { + return Error("__DATA_CONST segment missing SG_READ_ONLY flag"); + } + } + } + } + + // check for vmaddr wrapping + if ( (seg->vmaddr + seg->vmsize) < seg->vmaddr ) + return Error("'%s' segment vm range wraps", seg->segname); + + // check sections are within its segment + const SC* const sectionsStart = (SC*)((char*)seg + sizeof(SG)); + const SC* const sectionsEnd = §ionsStart[seg->nsects]; + for ( const SC* sect = sectionsStart; (sect < sectionsEnd); ++sect ) { + if ( (int64_t)(sect->size) < 0 ) { + return Error("section '%s' size too large 0x%lX", sect->sectname, (size_t)sect->size); + } + else if ( sect->addr < seg->vmaddr ) { + return Error("section '%s' start address 0x%lX is before containing segment's address 0x%0lX", sect->sectname, (size_t)sect->addr, (size_t)seg->vmaddr); + } + else if ( policy.enforceSectionsInSegment() && (sect->addr + sect->size > seg->vmaddr + seg->vmsize) ) { + return Error("section '%s' end address 0x%lX is beyond containing segment's end address 0x%0lX", sect->sectname, (size_t)(sect->addr + sect->size), (size_t)(seg->vmaddr + seg->vmsize)); + } + } + + return Error::none(); +} + +#endif // !TARGET_OS_EXCLAVEKIT +struct Interval +{ + bool overlaps(const Interval& other) const; + uint64_t start; + uint64_t end; +}; + +bool Interval::overlaps(const Interval& other) const +{ + return ((other.start < this->end) && (other.end > this->start)); +} + +Error Header::validSemanticsSegments(const Policy& policy, uint64_t fileSize) const +{ + // check each segment load command in isolation + struct SegRange + { + Interval vm; + Interval file; + const char* name; + }; + STACK_ALLOC_OVERFLOW_SAFE_ARRAY(SegRange, ranges, 12); + __block Error lcError; + __block bool hasTEXT = false; + __block bool hasLINKEDIT = false; + __block uintptr_t segmentIndexText = 0; + __block uintptr_t segmentIndexLinkedit = 0; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* seg64 = (segment_command_64*)cmd; + if ( strcmp(seg64->segname, "__TEXT") == 0 ) { + hasTEXT = true; + segmentIndexText = ranges.count(); + } + else if ( strcmp(seg64->segname, "__LINKEDIT") == 0 ) { + hasLINKEDIT = true; + segmentIndexLinkedit = ranges.count(); + } + lcError = validSegment(policy, fileSize, seg64); + ranges.push_back({ { seg64->vmaddr, seg64->vmaddr + seg64->vmsize }, { seg64->fileoff, seg64->fileoff + seg64->filesize }, seg64->segname }); + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* seg32 = (segment_command*)cmd; + if ( strcmp(seg32->segname, "__TEXT") == 0 ) { + hasTEXT = true; + segmentIndexText = ranges.count(); + } + else if ( strcmp(seg32->segname, "__LINKEDIT") == 0 ) { + hasLINKEDIT = true; + segmentIndexLinkedit = ranges.count(); + } + lcError = validSegment(policy, fileSize, seg32); + ranges.push_back({ { seg32->vmaddr, seg32->vmaddr + seg32->vmsize }, { seg32->fileoff, seg32->fileoff + seg32->filesize }, seg32->segname }); + } + if ( lcError ) + stop = true; + }); + if ( lcError ) + return std::move(lcError); + + // dynamic binaries have further restrictions + if ( isDyldManaged() ) { + if ( hasTEXT ) { + if ( ranges[segmentIndexText].file.start != 0 ) + return Error("__TEXT segment fileoffset is not zero"); + const uint32_t headerAndLCSize = machHeaderSize() + mh.sizeofcmds; + if ( ranges[segmentIndexText].file.end < headerAndLCSize ) + return Error("load commands do not fit in __TEXT segment"); + } + else { + return Error("missing __TEXT segment"); + } + // FIXME: LINKEDIT checks need to move to Analyzer + //if ( !hasLINKEDIT ) + // return Error("missing __LINKEDIT segment"); + } + + // check for overlapping segments, by looking at every possible pair of segments + for ( const SegRange& r1 : ranges ) { + for ( const SegRange& r2 : ranges ) { + if ( &r1 == &r2 ) + continue; + if ( r1.vm.overlaps(r2.vm) ) + return Error("vm range of segment '%s' overlaps segment '%s'", r1.name, r2.name); + if ( r1.file.overlaps(r2.file) ) + return Error("file range of segment '%s' overlaps segment '%s'", r1.name, r2.name); + } + } + + // check segment load command order matches file content order which matches vm order + // skip dyld cache because segments are moved around too much + if ( policy.enforceSegmentOrderMatchesLoadCmds() && !inDyldCache() ) { + const SegRange* last = nullptr; + for ( const SegRange& r : ranges ) { + if ( last != nullptr ) { + if ( (r.file.start < last->file.start) && (r.file.start != r.file.end) ) + return Error("segment '%s' file offset out of order", r.name); + if ( r.vm.start < last->vm.start ) { + if ( isFileSet() && (strcmp(r.name, "__PRELINK_INFO") == 0) ) { + // __PRELINK_INFO may have no vmaddr set + } + else { + return Error("segment '%s' vm address out of order", r.name); + } + } + } + last = &r; + } + } + + return Error::none(); +} + +Error Header::validSemanticsMain(const Policy& policy) const +{ + if ( this->inDyldCache() && policy.enforceMainFlagsCorrect() ) + return Error("MH_EXECUTE has MH_DYLIB_IN_CACHE bit set"); + + // validate the correct number of LC_MAIN or LC_UNIXTHREAD + __block Error lcError; + __block uint64_t startAddress = 0; + __block const entry_point_command* mainCmd = nullptr; + __block const thread_command* threadCmd = nullptr; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + switch ( cmd->cmd ) { + case LC_MAIN: + if ( mainCmd != nullptr ) + lcError = Error("multiple LC_MAIN load commands"); + mainCmd = (entry_point_command*)cmd; + break; + case LC_UNIXTHREAD: + if ( threadCmd != nullptr ) + lcError = Error("multiple LC_UNIXTHREAD load commands"); + threadCmd = (thread_command*)cmd; + if ( !entryAddrFromThreadCmd(threadCmd, startAddress) ) + lcError = Error("invalid LC_UNIXTHREAD"); + break; + } + }); + if ( lcError ) + return std::move(lcError); + if ( (mainCmd != nullptr) && (threadCmd != nullptr) ) + return Error("can't have LC_MAIN and LC_UNIXTHREAD load commands"); + if ( this->builtForPlatform(Platform::driverKit) ) { + if ( (mainCmd != nullptr) || (threadCmd != nullptr) ) + return Error("LC_MAIN not allowed for driverkit"); + } + else { + if ( (mainCmd == nullptr) && (threadCmd == nullptr) ) + return Error("missing LC_MAIN or LC_UNIXTHREAD in main executable"); + } + + // FIXME: validate LC_MAIN or LC_UNIXTHREAD points into executable segment + return Error::none(); +} + +Error Header::validSemanticsLinkerOptions(const Policy& policy) const +{ + __block Error lcError; + + forEachLoadCommandSafe(^(const load_command *cmd, bool &stop) { + if ( cmd->cmd == LC_LINKER_OPTION ) { + const char* begin = (char*)cmd + sizeof(linker_option_command); + const char* end = (char*)cmd + cmd->cmdsize; + const uint32_t count = ((linker_option_command*)cmd)->count; + for ( uint32_t i = 0; i < count; ++i ) { + const char* next = begin + strlen(begin) + 1; + if ( next > end ) { + lcError = Error("malformed LC_LINKER_OPTION command"); + stop = true; + return; + } + begin = next; + } + } + }); + + return std::move(lcError); +} + +Error Header::forEachLoadCommand(void (^callback)(const load_command* cmd, bool& stop)) const +{ + bool stop = false; + const load_command* startCmds = nullptr; + if ( mh.magic == MH_MAGIC_64 ) + startCmds = (load_command*)((char*)this + sizeof(mach_header_64)); + else if ( mh.magic == MH_MAGIC ) + startCmds = (load_command*)((char*)this + sizeof(mach_header)); + else if ( hasMachOBigEndianMagic() ) + return Error("big endian mach-o file"); + else { + const uint32_t* h = (uint32_t*)this; + return Error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h[1]); + } + if ( mh.filetype > 12 ) + return Error("unknown mach-o filetype (%u)", mh.filetype); + // const uint32_t ptrSize = this->pointerSize(); + const load_command* const cmdsEnd = (load_command*)((char*)startCmds + mh.sizeofcmds); + const load_command* cmd = startCmds; + for ( uint32_t i = 1; (i <= mh.ncmds) && !stop; ++i ) { + const load_command* nextCmd = (load_command*)((char*)cmd + cmd->cmdsize); + if ( cmd >= cmdsEnd ) { + return Error("malformed load command (%d of %d) at %p with mh=%p, off end of load commands", i, mh.ncmds, cmd, this); + } + if ( cmd->cmdsize < 8 ) { + return Error("malformed load command (%d of %d) at %p with mh=%p, size (0x%X) too small", i, mh.ncmds, cmd, this, cmd->cmdsize); + } +#if 0 + // check the cmdsize is pointer aligned + if ( checks.pointerAlignedLoadCommands ) { + if ( (cmd->cmdsize % ptrSize) != 0 ) { + return Error("malformed load command (%d of %d) at %p with mh=%p, size (0x%X) is not pointer sized", i, mh.ncmds, cmd, this, cmd->cmdsize); + } + } +#endif + if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) { + return Error("malformed load command (%d of %d) at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, mh.ncmds, cmd, this, cmd->cmdsize, cmdsEnd); + } + callback(cmd, stop); + cmd = nextCmd; + } + return Error::none(); +} + +// This forEach is only used after the load commands have been validated, so no need to return Error and handle it +void Header::forEachLoadCommandSafe(void (^callback)(const load_command* cmd, bool& stop)) const +{ + if ( Error err = forEachLoadCommand(callback) ) + assert("Header::forEachLoadCommand()"); +} + +bool Header::hasLoadCommand(uint32_t cmdNum) const +{ + __block bool hasLC = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == cmdNum ) { + hasLC = true; + stop = true; + } + }); + return hasLC; +} + +bool Header::isStaticExecutable() const +{ + if ( mh.filetype != MH_EXECUTE ) + return false; + + // static executables do not have dyld load command + return !hasLoadCommand(LC_LOAD_DYLINKER); +} + +// +// MARK: --- methods that read Platform load commands --- +// + +void Header::forEachPlatformLoadCommand(void (^handler)(Platform platform, Version32 minOS, Version32 sdk)) const +{ + __block bool foundPlatform = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + const build_version_command* buildCmd = (build_version_command*)cmd; + const version_min_command* versCmd = (version_min_command*)cmd; + uint32_t sdk; + switch ( cmd->cmd ) { + case LC_BUILD_VERSION: + handler(Platform(buildCmd->platform), Version32(buildCmd->minos), Version32(buildCmd->sdk)); + foundPlatform = true; + break; + case LC_VERSION_MIN_MACOSX: + sdk = versCmd->sdk; + // The original LC_VERSION_MIN_MACOSX did not have an sdk field, assume sdk is same as minOS for those old binaries + if ( sdk == 0 ) + sdk = versCmd->version; + handler(Platform::macOS, Version32(versCmd->version), Version32(sdk)); + foundPlatform = true; + break; + case LC_VERSION_MIN_IPHONEOS: + if ( (mh.cputype == CPU_TYPE_X86_64) || (mh.cputype == CPU_TYPE_I386) ) + handler(Platform::iOS_simulator, Version32(versCmd->version), Version32(versCmd->sdk)); // old sim binary + else + handler(Platform::iOS, Version32(versCmd->version), Version32(versCmd->sdk)); + foundPlatform = true; + break; + case LC_VERSION_MIN_TVOS: + if ( mh.cputype == CPU_TYPE_X86_64 ) + handler(Platform::tvOS_simulator, Version32(versCmd->version), Version32(versCmd->sdk)); // old sim binary + else + handler(Platform::tvOS, Version32(versCmd->version), Version32(versCmd->sdk)); + foundPlatform = true; + break; + case LC_VERSION_MIN_WATCHOS: + if ( (mh.cputype == CPU_TYPE_X86_64) || (mh.cputype == CPU_TYPE_I386) ) + handler(Platform::watchOS_simulator, Version32(versCmd->version), Version32(versCmd->sdk)); // old sim binary + else + handler(Platform::watchOS, Version32(versCmd->version), Version32(versCmd->sdk)); + foundPlatform = true; + break; + } + }); +#ifndef BUILDING_MACHO_WRITER // no implicit platforms in static linker + if ( !foundPlatform ) { + // old binary with no explicit platform +#if TARGET_OS_OSX + if ( (mh.cputype == CPU_TYPE_X86_64) | (mh.cputype == CPU_TYPE_I386) ) + handler(Platform::macOS, Version32(10, 5), Version32(10, 5)); // guess it is a macOS 10.5 binary + // + // The Go linker emits non-standard binaries without a platform and we have to live with it. + if ( mh.cputype == CPU_TYPE_ARM64 ) + handler(Platform::macOS, Version32(11, 0), Version32(11, 0)); // guess it is a macOS 11.0 binary +#endif + } +#endif +} + +bool Header::builtForPlatform(Platform reqPlatform, bool onlyOnePlatform) const +{ + PlatformAndVersions pvs = platformAndVersions(); + + if ( pvs.platform == reqPlatform ) + return true; + + if ( onlyOnePlatform ) + return false; + + __block bool match = false; + pvs.unzip(^(PlatformAndVersions pvers) { + match |= pvers.platform == reqPlatform; + }); + + return match; +} + +bool Header::isZippered() const +{ + return platformAndVersions().platform == Platform::zippered; +} + +bool Header::allowsAlternatePlatform() const +{ + __block bool result = false; + this->forEachSection(^(const SectionInfo& info, bool& stop) { + if ( (info.sectionName == "__allow_alt_plat") && info.segmentName.starts_with("__DATA") ) { + result = true; + stop = true; + } + }); + return result; +} + +const char* Header::installName() const +{ + const char* name; + Version32 compatVersion; + Version32 currentVersion; + if ( getDylibInstallName(&name, &compatVersion, ¤tVersion) ) + return name; + return nullptr; +} + +bool Header::getDylibInstallName(const char** installName, Version32* compatVersion, Version32* currentVersion) const +{ + __block bool found = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_ID_DYLIB ) { + const dylib_command* dylibCmd = (dylib_command*)cmd; + *compatVersion = Version32(dylibCmd->dylib.compatibility_version); + *currentVersion = Version32(dylibCmd->dylib.current_version); + *installName = (char*)dylibCmd + dylibCmd->dylib.name.offset; + found = true; + stop = true; + } + }); + return found; +} + +bool Header::getUuid(uuid_t uuid) const +{ + __block bool found = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_UUID ) { + const uuid_command* uc = (const uuid_command*)cmd; + memcpy(uuid, uc->uuid, sizeof(uuid_t)); + found = true; + stop = true; + } + }); + if ( !found ) + bzero(uuid, sizeof(uuid_t)); + return found; +} + + +const char* Header::dependentDylibLoadPath(uint32_t depIndex) const +{ + __block uint32_t curIndex = 0; + __block const char* result = nullptr; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + switch ( cmd->cmd ) { + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: { + const dylib_command* dylibCmd = (dylib_command*)cmd; + if ( curIndex == depIndex ) + result = (char*)dylibCmd + dylibCmd->dylib.name.offset; + ++curIndex; + } break; + } + }); + return result; +} + +uint32_t Header::dependentDylibCount(bool* allDepsAreNormal) const +{ + if ( allDepsAreNormal != nullptr ) + *allDepsAreNormal = true; + __block unsigned count = 0; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + switch ( cmd->cmd ) { + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + if ( allDepsAreNormal != nullptr ) + *allDepsAreNormal = false; // record if any linkages were weak, re-export, or upward + ++count; + break; + case LC_LOAD_DYLIB: + ++count; + break; + } + }); + return count; +} + +void Header::forEachDependentDylib(void (^callback)(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, + Version32 compatVersion, Version32 curVersion, bool& stop)) const +{ + __block unsigned count = 0; + __block bool stopped = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + switch ( cmd->cmd ) { + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: { + const dylib_command* dylibCmd = (dylib_command*)cmd; + const char* loadPath = (char*)dylibCmd + dylibCmd->dylib.name.offset; + callback(loadPath, (cmd->cmd == LC_LOAD_WEAK_DYLIB), (cmd->cmd == LC_REEXPORT_DYLIB), (cmd->cmd == LC_LOAD_UPWARD_DYLIB), + Version32(dylibCmd->dylib.compatibility_version), Version32(dylibCmd->dylib.current_version), stop); + ++count; + if ( stop ) + stopped = true; + } break; + } + }); + // everything must link with something + if ( (count == 0) && !stopped ) { + // The dylibs that make up libSystem can link with nothing + // except for dylibs in libSystem.dylib which are ok to link with nothing (they are on bottom) + if ( this->builtForPlatform(Platform::driverKit, true) ) { + if ( !this->isDylib() || (strncmp(this->installName(), "/System/DriverKit/usr/lib/system/", 33) != 0) ) + callback("/System/DriverKit/usr/lib/libSystem.B.dylib", false, false, false, Version32(1, 0), Version32(1, 0), stopped); + } + else { + if ( !this->isDylib() || (strncmp(this->installName(), "/usr/lib/system/", 16) != 0) ) + callback("/usr/lib/libSystem.B.dylib", false, false, false, Version32(1, 0), Version32(1, 0), stopped); + } + } +} + +void Header::forDyldEnv(void (^callback)(const char* envVar, bool& stop)) const +{ + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_DYLD_ENVIRONMENT ) { + const dylinker_command* envCmd = (dylinker_command*)cmd; + const char* keyEqualsValue = (char*)envCmd + envCmd->name.offset; + // only process variables that start with DYLD_ and end in _PATH + if ( (strncmp(keyEqualsValue, "DYLD_", 5) == 0) ) { + const char* equals = strchr(keyEqualsValue, '='); + if ( equals != NULL ) { + if ( strncmp(&equals[-5], "_PATH", 5) == 0 ) { + callback(keyEqualsValue, stop); + } + } + } + } + }); +} + +bool Header::entryAddrFromThreadCmd(const thread_command* cmd, uint64_t& addr) const +{ + const uint32_t* regs32 = (uint32_t*)(((char*)cmd) + 16); + const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16); + uint32_t flavor = *((uint32_t*)(((char*)cmd) + 8)); + switch ( mh.cputype ) { + case CPU_TYPE_I386: + if ( flavor == 1 ) { // i386_THREAD_STATE + addr = regs32[10]; // i386_thread_state_t.eip + return true; + } + break; + case CPU_TYPE_X86_64: + if ( flavor == 4 ) { // x86_THREAD_STATE64 + addr = regs64[16]; // x86_thread_state64_t.rip + return true; + } + break; + case CPU_TYPE_ARM: + if ( flavor == 1 ) { // ARM_THREAD_STATE + addr = regs32[15]; // arm_thread_state_t.pc + return true; + } + break; + case CPU_TYPE_ARM64: + if ( flavor == 6 ) { // ARM_THREAD_STATE64 + addr = regs64[32]; // arm_thread_state64_t.__pc + return true; + } + break; + } + return false; +} + +// returns false if entry point not found +bool Header::getEntry(uint64_t& offset, bool& usesCRT) const +{ + __block bool result = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_MAIN ) { + entry_point_command* mainCmd = (entry_point_command*)cmd; + offset = mainCmd->entryoff; + usesCRT = false; + result = true; + stop = true; + } + else if ( cmd->cmd == LC_UNIXTHREAD ) { + uint64_t startAddress; + if ( entryAddrFromThreadCmd((thread_command*)cmd, startAddress) ) { + offset = startAddress - preferredLoadAddress(); + usesCRT = true; + result = true; + } + stop = true; + } + }); + return result; +} + +bool Header::hasCodeSignature(uint32_t& fileOffset, uint32_t& size) const +{ + __block bool result = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_CODE_SIGNATURE ) { + linkedit_data_command* sigCmd = (linkedit_data_command*)cmd; + fileOffset = sigCmd->dataoff; + size = sigCmd->datasize; + result = true; + stop = true; + } + }); + // FIXME: may need to ignore codesigs from pre 10.9 macOS binaries + return result; +} + +bool Header::hasIndirectSymbolTable(uint32_t& fileOffset, uint32_t& count) const +{ + __block bool result = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_DYSYMTAB) { + dysymtab_command* dySymCmd = (dysymtab_command*)cmd; + fileOffset = dySymCmd->indirectsymoff; + count = dySymCmd->nindirectsyms; + result = true; + stop = true; + } + }); + return result; +} + +bool Header::hasSplitSegInfo() const +{ + __block bool result = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_SPLIT_INFO) { + result = true; + stop = true; + } + }); + return result; +} + +bool Header::hasAtomInfo(uint32_t& fileOffset, uint32_t& size) const +{ + __block bool result = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_ATOM_INFO) { + linkedit_data_command* sigCmd = (linkedit_data_command*)cmd; + fileOffset = sigCmd->dataoff; + size = sigCmd->datasize; + result = true; + stop = true; + } + }); + return result; +} + + +uint32_t Header::segmentCount() const +{ + __block uint32_t count = 0; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + switch ( cmd->cmd ) { + case LC_SEGMENT: + case LC_SEGMENT_64: + ++count; + break; + } + }); + return count; +} + +uint64_t Header::preferredLoadAddress() const +{ + __block uint64_t textVmAddr = 0; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* segCmd = (segment_command_64*)cmd; + if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { + textVmAddr = segCmd->vmaddr; + stop = true; + } + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { + textVmAddr = segCmd->vmaddr; + stop = true; + } + } + }); + return textVmAddr; +} + +int64_t Header::getSlide() const +{ + return (long)this - (long)(this->preferredLoadAddress()); +} + +bool Header::hasDataConst() const +{ + __block bool result = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* segCmd = (segment_command_64*)cmd; + if ( (segCmd->flags & SG_READ_ONLY) != 0 ) + result = true; + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + if ( (segCmd->flags & SG_READ_ONLY) != 0 ) + result = true; + } + }); + return result; +} + +std::string_view Header::segmentName(uint32_t segIndex) const +{ + __block std::string_view result; + __block uint32_t segCount = 0; + this->forEachSegment(^(const SegmentInfo& info, bool& stop) { + if ( segIndex == segCount ) { + result = info.segmentName; + stop = true; + } + ++segCount; + }); + return result; +} + +// LC_SEGMENT stores names as char[16] potentially without a null terminator. This returns a string_view for the given name +static std::string_view name16(const char name[16]) +{ + size_t length = strnlen(name, 16); + return std::string_view(name, length); +} + +void Header::forEachSegment(void (^callback)(const SegmentInfo& infos, bool& stop)) const +{ + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* segCmd = (segment_command_64*)cmd; + SegmentInfo segInfo { .segmentName=name16(segCmd->segname), .vmaddr=segCmd->vmaddr, .vmsize=segCmd->vmsize, + .fileOffset=(uint32_t)segCmd->fileoff, .fileSize=(uint32_t)segCmd->filesize, .flags=segCmd->flags, .perms=(uint8_t)segCmd->initprot }; + callback(segInfo, stop); + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + SegmentInfo segInfo { .segmentName=name16(segCmd->segname), .vmaddr=segCmd->vmaddr, .vmsize=segCmd->vmsize, + .fileOffset=segCmd->fileoff, .fileSize=segCmd->filesize, .flags=segCmd->flags, .perms=(uint8_t)segCmd->initprot }; + callback(segInfo, stop); + } + }); +} + +void Header::forEachSection(void (^callback)(const SectionInfo&, bool& stop)) const +{ + __block uint64_t prefLoadAddr = 0; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* segCmd = (segment_command_64*)cmd; + if ( strcmp(segCmd->segname, "__TEXT") == 0 ) + prefLoadAddr = segCmd->vmaddr; + const section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64)); + const section_64* const sectionsEnd = §ionsStart[segCmd->nsects]; + + for ( const section_64* sect = sectionsStart; !stop && (sect < sectionsEnd); ++sect ) { + std::string_view sectName = name16(sect->sectname); + std::string_view segName = name16(sect->segname); + SectionInfo info = { segName, sectName, (uint32_t)segCmd->initprot, sect->flags, sect->align, sect->addr - prefLoadAddr, sect->size, sect->offset, + sect->reloff, sect->nreloc, sect->reserved1, sect->reserved2}; + callback(info, stop); + } + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + if ( strcmp(segCmd->segname, "__TEXT") == 0 ) + prefLoadAddr = segCmd->vmaddr; + const section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command)); + const section* const sectionsEnd = §ionsStart[segCmd->nsects]; + for ( const section* sect = sectionsStart; !stop && (sect < sectionsEnd); ++sect ) { + std::string_view sectName = name16(sect->sectname); + std::string_view segName = name16(sect->segname); + SectionInfo info = { segName, sectName, (uint32_t)segCmd->initprot, sect->flags, sect->align, sect->addr - prefLoadAddr, sect->size, sect->offset, + sect->reloff, sect->nreloc, sect->reserved1, sect->reserved2}; + callback(info, stop); + } + } + }); +} + +// add any LINKEDIT content file-offset in load commands to this to get content +const uint8_t* Header::computeLinkEditBias(bool zeroFillExpanded) const +{ + // When there is no zerofill expansion, just add fileoffset of LINKEDIT content to mach_header to get content + // If there is zerofill expansion, then zerofillExpansionAmount() needs to be added in too + if ( zeroFillExpanded ) + return (uint8_t*)this + zerofillExpansionAmount(); + else + return (uint8_t*)this; +} + +// When loaded by dyld, LINKEDIT is farther from mach_header than in file +bool Header::hasZerofillExpansion() const +{ + return (zerofillExpansionAmount() != 0); +} + +uint64_t Header::zerofillExpansionAmount() const +{ + // need to find LINKEDIT and TEXT to compute difference of file offsets vs vm offsets + __block uint64_t result = 0; + __block uint64_t textVmAddr = 0; + __block uint64_t textFileOffset = 0; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + const segment_command_64* segCmd = (segment_command_64*)cmd; + if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { + textVmAddr = segCmd->vmaddr; + textFileOffset = segCmd->fileoff; + } + else if ( strcmp(segCmd->segname, "__LINKEDIT") == 0 ) { + uint64_t vmOffsetToLinkedit = segCmd->vmaddr - textVmAddr; + uint64_t fileOffsetToLinkedit = segCmd->fileoff; + result = vmOffsetToLinkedit - fileOffsetToLinkedit; + stop = true; + } + } + else if ( cmd->cmd == LC_SEGMENT ) { + const segment_command* segCmd = (segment_command*)cmd; + if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { + textVmAddr = segCmd->vmaddr; + textFileOffset = segCmd->fileoff; + } + else if ( strcmp(segCmd->segname, "__LINKEDIT") == 0 ) { + uint64_t vmOffsetToLinkedit = segCmd->vmaddr - textVmAddr; + uint64_t fileOffsetToLinkedit = segCmd->fileoff - textFileOffset; + result = vmOffsetToLinkedit - fileOffsetToLinkedit; + stop = true; + } + } + }); + return result; +} + +bool Header::hasCustomStackSize(uint64_t& size) const { + __block bool result = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_MAIN ) { + const entry_point_command* entryPointCmd = (entry_point_command*)cmd; + size = entryPointCmd->stacksize; + result = true; + stop = true; + } + }); + return result; +} + +bool Header::isRestricted() const +{ + __block bool result = false; + this->forEachSection(^(const SectionInfo& info, bool& stop) { + if ( (info.segmentName == "__RESTRICT") && (info.sectionName == "__restrict") ) { + result = true; + stop = true; + } + }); + return result; +} + +bool Header::hasInterposingTuples() const +{ + __block bool hasInterposing = false; + this->forEachSection(^(const SectionInfo& info, bool& stop) { + if ( ((info.flags & SECTION_TYPE) == S_INTERPOSING) || ((info.sectionName == "__interpose") && (info.segmentName.starts_with("__DATA") || info.segmentName.starts_with("__AUTH"))) ) { + hasInterposing = true; + stop = true; + } + }); + return hasInterposing; +} + +bool Header::hasObjC() const +{ + __block bool hasObjCInfo = false; + this->forEachSection(^(const SectionInfo& info, bool& stop) { + if ( (info.sectionName == "__objc_imageinfo") && info.segmentName.starts_with("__DATA") ) { + hasObjCInfo = true; + stop = true; + } + }); + return hasObjCInfo; +} + +bool Header::hasEncryptionInfo(uint32_t& cryptId, uint32_t& textOffset, uint32_t& size) const +{ + if ( const encryption_info_command* encCmd = findFairPlayEncryptionLoadCommand() ) { + cryptId = encCmd->cryptid; + textOffset = encCmd->cryptoff; + size = encCmd->cryptsize; + return true; + } + textOffset = 0; + size = 0; + return false; +} + +bool Header::isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) const +{ + // Note: cryptid is 0 in just-built apps. The AppStore sets cryptid to 1 + uint32_t cryptId = 0; + return hasEncryptionInfo(cryptId, textOffset, size) && cryptId == 1; +} + +bool Header::canBeFairPlayEncrypted() const +{ + return (findFairPlayEncryptionLoadCommand() != nullptr); +} + +const encryption_info_command* Header::findFairPlayEncryptionLoadCommand() const +{ + __block const encryption_info_command* result = nullptr; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( (cmd->cmd == LC_ENCRYPTION_INFO) || (cmd->cmd == LC_ENCRYPTION_INFO_64) ) { + result = (encryption_info_command*)cmd; + stop = true; + } + }); + return result; +} + +bool Header::hasChainedFixups() const +{ + // arm64e always uses chained fixups + if ( Architecture(&mh) == Architecture::arm64e ) { + // Not all binaries have fixups at all so check for the load commands + return hasLoadCommand(LC_DYLD_INFO_ONLY) || hasLoadCommand(LC_DYLD_CHAINED_FIXUPS); + } + return hasLoadCommand(LC_DYLD_CHAINED_FIXUPS); +} + +bool Header::hasChainedFixupsLoadCommand() const +{ + return hasLoadCommand(LC_DYLD_CHAINED_FIXUPS); +} + +bool Header::hasOpcodeFixups() const +{ + return hasLoadCommand(LC_DYLD_INFO_ONLY) || hasLoadCommand(LC_DYLD_INFO); +} + +void Header::forEachRPath(void (^callback)(const char* rPath, bool& stop)) const +{ + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_RPATH ) { + const char* rpath = (char*)cmd + ((struct rpath_command*)cmd)->path.offset; + callback(rpath, stop); + } + }); +} + +void Header::forEachLinkerOption(void (^callback)(const char* opt, bool& stop)) const +{ + forEachLoadCommandSafe(^(const load_command *cmd, bool &stop) { + if ( cmd->cmd == LC_LINKER_OPTION ) { + const char* begin = (char*)cmd + sizeof(linker_option_command); + const uint32_t count = ((linker_option_command*)cmd)->count; + for ( uint32_t i = 0; i < count; ++i ) { + const char* next = begin + strlen(begin) + 1; + callback(begin, stop); + begin = next; + } + } + }); +} + +void Header::forAllowableClient(void (^callback)(const char* clientName, bool& stop)) const +{ + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SUB_CLIENT ) { + const char* clientName = (char*)cmd + ((struct sub_client_command*)cmd)->client.offset; + callback(clientName, stop); + } + }); +} + +const char* Header::umbrellaName() const +{ + __block const char* result = nullptr; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SUB_FRAMEWORK ) { + result = (char*)cmd + ((struct sub_framework_command*)cmd)->umbrella.offset; + } + }); + return result; +} + + +uint32_t Header::headerAndLoadCommandsSize() const +{ + return machHeaderSize() + mh.sizeofcmds; +} + +uint32_t Header::fileSize() const +{ + if ( isObjectFile() ) { + // .o files do not have LINKEDIT segment, so use end of symbol table as file size + __block uint32_t size = 0; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SYMTAB ) { + const symtab_command* symTab = (symtab_command*)cmd; + size = symTab->stroff + symTab->strsize; + stop = true; + } + }); + return size; + } + + // compute file size from LINKEDIT fileoffset + filesize + __block uint32_t lastSegmentOffset = 0; + __block uint32_t lastSegmentSize = 0; + forEachSegment(^(const SegmentInfo &infos, bool &stop) { + if ( infos.fileOffset >= lastSegmentOffset ) { + lastSegmentOffset = infos.fileOffset; + lastSegmentSize = std::max(infos.fileSize, lastSegmentSize); + } + }); + if ( lastSegmentSize == 0 ) + return headerAndLoadCommandsSize(); + + uint32_t size; + if ( __builtin_add_overflow(lastSegmentOffset, lastSegmentSize, &size) + || size < headerAndLoadCommandsSize() ) + assert("malformed mach-o, size smaller than header and load commands"); + + return size; +} + +// +// MARK: --- methods that create and modify --- +// + +#if BUILDING_MACHO_WRITER + +Header* Header::make(std::span buffer, uint32_t filetype, uint32_t flags, Architecture arch, bool addImplicitTextSegment) +{ + const size_t minHeaderAlignment = filetype == MH_OBJECT ? 8 : getpagesize(); + assert(((uint64_t)buffer.data() & (minHeaderAlignment - 1)) == 0); + assert(buffer.size() >= sizeof(mach_header_64)); + bzero(buffer.data(), buffer.size()); + Header& header = *(Header*)buffer.data(); + mach_header& mh = header.mh; + if ( arch.isBigEndian() ) { + mh.magic = arch.is64() ? MH_CIGAM_64 : MH_CIGAM; + mh.filetype = OSSwapBigToHostInt32(filetype); + mh.ncmds = 0; + mh.sizeofcmds = OSSwapBigToHostInt32(MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL); + mh.flags = OSSwapBigToHostInt32(flags); + arch.set(mh); + return &header; // can only construct mach_header for big-endian + } + else { + mh.magic = arch.is64() ? MH_MAGIC_64 : MH_MAGIC; + mh.filetype = filetype; + mh.ncmds = 0; + mh.sizeofcmds = 0; + mh.flags = flags; + arch.set(mh); + } + if ( addImplicitTextSegment && (filetype != MH_OBJECT) ) { + SegmentInfo segInfo { .segmentName="__TEXT", .vmaddr=0, .vmsize=0x1000, .fileOffset=0, .fileSize=0x1000, .perms=(VM_PROT_READ | VM_PROT_EXECUTE) }; + header.addSegment(segInfo, std::array { "__text" }); + } + + return &header; +} + +void Header::save(char savedPath[PATH_MAX]) const +{ + ::strcpy(savedPath, "/tmp/mocko-XXXXXX"); + int fd = ::mkstemp(savedPath); + if ( fd != -1 ) { + ::pwrite(fd, this, sizeof(Header), 0); + ::close(fd); + } +} + +uint32_t Header::pointerAligned(uint32_t value) const +{ + // mach-o requires all load command sizes to be a multiple the pointer size + if ( is64() ) + return ((value + 7) & (-8)); + else + return ((value + 3) & (-4)); +} + +load_command* Header::firstLoadCommand() +{ + if ( mh.magic == MH_MAGIC ) + return (load_command*)((uint8_t*)this + sizeof(mach_header)); + else + return (load_command*)((uint8_t*)this + sizeof(mach_header_64)); +} + +// creates space for a new load command, but does not fill in its payload +load_command* Header::appendLoadCommand(uint32_t cmd, uint32_t cmdSize) +{ + load_command* thisCmd = (load_command*)((uint8_t*)firstLoadCommand() + mh.sizeofcmds); + thisCmd->cmd = cmd; + thisCmd->cmdsize = cmdSize; + mh.ncmds += 1; + mh.sizeofcmds += cmdSize; + + return thisCmd; +} + +// copies a new load command from another +void Header::appendLoadCommand(const load_command* lc) +{ + load_command* thisCmd = (load_command*)((uint8_t*)firstLoadCommand() + mh.sizeofcmds); + ::memcpy(thisCmd, lc, lc->cmdsize); + mh.ncmds += 1; + mh.sizeofcmds += lc->cmdsize; +} + +void Header::addBuildVersion(Platform platform, Version32 minOS, Version32 sdk, std::span tools) +{ + assert(platform != Platform::zippered && "can't add a build command for Platform::zippered, it must be split"); + uint32_t lcSize = (uint32_t)(sizeof(build_version_command) + tools.size() * sizeof(build_tool_version)); + build_version_command* bv = (build_version_command*)appendLoadCommand(LC_BUILD_VERSION, lcSize); + bv->platform = platform.value(); + bv->minos = minOS.value(); + bv->sdk = sdk.value(); + bv->ntools = (uint32_t)tools.size(); + if ( bv->ntools != 0 ) + memcpy((uint8_t*)bv + sizeof(build_version_command), &tools[0], tools.size() * sizeof(build_tool_version)); +} + +void Header::addMinVersion(Platform platform, Version32 minOS, Version32 sdk) +{ + version_min_command vc; + vc.cmdsize = sizeof(version_min_command); + vc.version = minOS.value(); + vc.sdk = sdk.value(); + if ( platform == Platform::macOS ) + vc.cmd = LC_VERSION_MIN_MACOSX; + else if ( platform == Platform::iOS ) + vc.cmd = LC_VERSION_MIN_IPHONEOS; + else if ( platform == Platform::watchOS ) + vc.cmd = LC_VERSION_MIN_WATCHOS; + else if ( platform == Platform::tvOS ) + vc.cmd = LC_VERSION_MIN_TVOS; + else + assert(0 && "unknown platform"); + appendLoadCommand((load_command*)&vc); +} + +void Header::setHasThreadLocalVariables() +{ + assert(mh.filetype != MH_OBJECT); + mh.flags |= MH_HAS_TLV_DESCRIPTORS; +} + +void Header::setHasWeakDefs() +{ + assert(mh.filetype != MH_OBJECT); + mh.flags |= MH_WEAK_DEFINES; +} + +void Header::setUsesWeakDefs() +{ + assert(mh.filetype != MH_OBJECT); + mh.flags |= MH_BINDS_TO_WEAK; +} + +void Header::setAppExtensionSafe() +{ + assert(mh.filetype == MH_DYLIB); + mh.flags |= MH_APP_EXTENSION_SAFE; +} + +void Header::setSimSupport() +{ + assert(mh.filetype == MH_DYLIB); + mh.flags |= MH_SIM_SUPPORT; +} + +void Header::setNoReExportedDylibs() +{ + assert(mh.filetype == MH_DYLIB); + mh.flags |= MH_NO_REEXPORTED_DYLIBS; +} + +void Header::addPlatformInfo(Platform platform, Version32 minOS, Version32 sdk, std::span tools) +{ + Architecture arch(&mh); + Policy policy(arch, { platform, minOS, sdk }, mh.filetype); + switch ( policy.useBuildVersionLoadCommand() ) { + case Policy::preferUse: + case Policy::mustUse: + // three macOS dylibs under libSystem need to be built with old load commands to support old simulator runtimes + if ( isSimSupport() && (platform == Platform::macOS) && ((arch == Architecture::x86_64) || (arch == Architecture::i386)) ) + addMinVersion(platform, minOS, sdk); + else + addBuildVersion(platform, minOS, sdk, tools); + break; + case Policy::preferDontUse: + case Policy::mustNotUse: + addMinVersion(platform, minOS, sdk); + break; + } +} + +void Header::addNullUUID() +{ + uuid_command uc; + uc.cmd = LC_UUID; + uc.cmdsize = sizeof(uuid_command); + bzero(uc.uuid, 16); + appendLoadCommand((load_command*)&uc); +} + +void Header::addUniqueUUID(uuid_t copyOfUUID) +{ + uuid_command uc; + uc.cmd = LC_UUID; + uc.cmdsize = sizeof(uuid_command); + uuid_generate_random(uc.uuid); + appendLoadCommand((load_command*)&uc); + if ( copyOfUUID ) + memcpy(copyOfUUID, uc.uuid, sizeof(uuid_t)); +} + +void Header::updateUUID(uuid_t uuid) +{ + __block bool found = false; + forEachLoadCommandSafe(^(const load_command *cmd, bool &stop) { + if ( cmd->cmd == LC_UUID ) { + memcpy(((uuid_command*)cmd)->uuid, uuid, 16); + found = true; + stop = true; + } + }); + assert(found && "updateUUID called without a LC_UUID command"); +} + +void Header::addSegment(const SegmentInfo& info, std::span sectionNames) +{ + if ( is64() ) { + uint32_t lcSize = (uint32_t)(sizeof(segment_command_64) + sectionNames.size() * sizeof(section_64)); + segment_command_64* sc = (segment_command_64*)appendLoadCommand(LC_SEGMENT_64, lcSize); + strncpy(sc->segname, info.segmentName.data(), 16); + sc->vmaddr = info.vmaddr; + sc->vmsize = info.vmsize; + sc->fileoff = info.fileOffset; + sc->filesize = info.fileSize; + sc->initprot = info.perms; + sc->maxprot = info.perms; + sc->nsects = (uint32_t)sectionNames.size(); + sc->flags = info.flags; + section_64* const sect = (section_64*)((uint8_t*)sc + sizeof(struct segment_command_64)); + uint32_t sectionIndex = 0; + for ( const char* sectName : sectionNames ) { + strncpy(sect[sectionIndex].segname, info.segmentName.data(), 16); + strncpy(sect[sectionIndex].sectname, sectName, 16); + ++sectionIndex; + } + } + else { + uint32_t lcSize = (uint32_t)(sizeof(segment_command) + sectionNames.size() * sizeof(section)); + segment_command* sc = (segment_command*)appendLoadCommand(LC_SEGMENT, lcSize); + strncpy(sc->segname, info.segmentName.data(), 16); + sc->vmaddr = (uint32_t)info.vmaddr; + sc->vmsize = (uint32_t)info.vmsize; + sc->fileoff = info.fileOffset; + sc->filesize = info.fileSize; + sc->initprot = info.perms; + sc->maxprot = info.perms; + sc->nsects = (uint32_t)sectionNames.size(); + sc->flags = info.flags; + section* const sect = (section*)((uint8_t*)sc + sizeof(struct segment_command)); + uint32_t sectionIndex = 0; + for ( const char* sectName : sectionNames ) { + strncpy(sect[sectionIndex].segname, info.segmentName.data(), 16); + strncpy(sect[sectionIndex].sectname, sectName, 16); + ++sectionIndex; + } + } +} + +void Header::updateSection(const SectionInfo& info) +{ + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + segment_command_64* segCmd = (segment_command_64*)cmd; + if (info.segmentName == segCmd->segname) { + section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64)); + section_64* const sectionsEnd = §ionsStart[segCmd->nsects]; + for ( section_64* sect=sectionsStart; sect < sectionsEnd; ++sect ) { + if ( strncmp(info.sectionName.data(), sect->sectname, 16) == 0 ) { + sect->addr = info.address; + sect->size = info.size; + sect->offset = info.fileOffset; + sect->align = info.alignment; + sect->reloff = info.relocsOffset; + sect->nreloc = info.relocsCount; + sect->flags = info.flags; + sect->reserved1 = info.reserved1; + sect->reserved2 = info.reserved2; + sect->reserved3 = 0; + stop = true; + return; + } + } + } + } + else if ( cmd->cmd == LC_SEGMENT ) { + segment_command* segCmd = (segment_command*)cmd; + if (info.segmentName == segCmd->segname) { + section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command)); + section* const sectionsEnd = §ionsStart[segCmd->nsects]; + for ( section* sect=sectionsStart; sect < sectionsEnd; ++sect ) { + if ( strncmp(info.sectionName.data(), sect->sectname, 16) == 0 ) { + sect->addr = (uint32_t)info.address; + sect->size = (uint32_t)info.size; + sect->offset = info.fileOffset; + sect->align = info.alignment; + sect->reloff = info.relocsOffset; + sect->nreloc = info.relocsCount; + sect->flags = info.flags; + sect->reserved1 = info.reserved1; + sect->reserved2 = info.reserved2; + stop = true; + return; + } + } + } + } + }); +} + +void Header::updateSegment(const SegmentInfo& info) +{ + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT_64 ) { + segment_command_64* segCmd = (segment_command_64*)cmd; + if (info.segmentName == segCmd->segname) { + segCmd->vmaddr = info.vmaddr; + segCmd->vmsize = info.vmsize; + segCmd->fileoff = info.fileOffset; + segCmd->filesize = info.fileSize; + segCmd->initprot = info.perms; + segCmd->maxprot = info.perms; + stop = true; + return; + } + } + else if ( cmd->cmd == LC_SEGMENT ) { + segment_command* segCmd = (segment_command*)cmd; + if (info.segmentName == segCmd->segname) { + segCmd->vmaddr = (uint32_t)info.vmaddr; + segCmd->vmsize = (uint32_t)info.vmsize; + segCmd->fileoff = info.fileOffset; + segCmd->filesize = info.fileSize; + segCmd->initprot = info.perms; + segCmd->maxprot = info.perms; + stop = true; + return; + } + } + }); +} + + +void Header::addInstallName(const char* name, Version32 compatVers, Version32 currentVersion) +{ + uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylib_command) + strlen(name) + 1)); + dylib_command* ic = (dylib_command*)appendLoadCommand(LC_ID_DYLIB, alignedSize); + ic->dylib.name.offset = sizeof(dylib_command); + ic->dylib.current_version = currentVersion.value(); + ic->dylib.compatibility_version = compatVers.value(); + strcpy((char*)ic + ic->dylib.name.offset, name); +} + +void Header::addDependentDylib(const char* path, bool isWeak, bool isUpward, bool isReexport, Version32 compatVers, Version32 currentVersion) +{ + uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylib_command) + strlen(path) + 1)); + dylib_command* dc = (dylib_command*)appendLoadCommand(LC_LOAD_DYLIB, alignedSize); + if ( isReexport ) + dc->cmd = LC_REEXPORT_DYLIB; + else if ( isUpward ) + dc->cmd = LC_LOAD_UPWARD_DYLIB; + else if ( isWeak ) + dc->cmd = LC_LOAD_WEAK_DYLIB; + dc->dylib.name.offset = sizeof(dylib_command); + dc->dylib.current_version = currentVersion.value(); + dc->dylib.compatibility_version = compatVers.value(); + dc->dylib.timestamp = 2; // needs to be some constant value that is different than dylib id load command + strcpy((char*)dc + dc->dylib.name.offset, path); +} + +void Header::addLibSystem() +{ + addDependentDylib("/usr/lib/libSystem.B.dylib"); +} + +void Header::addDylibId(CString name, Version32 compatVers, Version32 currentVersion) +{ + uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylib_command) + name.size() + 1)); + dylib_command* dc = (dylib_command*)appendLoadCommand(LC_ID_DYLIB, alignedSize); + dc->dylib.name.offset = sizeof(dylib_command); + dc->dylib.timestamp = 1; // needs to be some constant value that is different than dependent dylib + dc->dylib.current_version = currentVersion.value(); + dc->dylib.compatibility_version = compatVers.value(); + strcpy((char*)dc + dc->dylib.name.offset, name.c_str()); +} + +void Header::addDyldID() +{ + const char* path = "/usr/lib/dyld"; + uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylinker_command) + strlen(path) + 1)); + dylinker_command* dc = (dylinker_command*)appendLoadCommand(LC_ID_DYLINKER, alignedSize); + dc->name.offset = sizeof(dylinker_command); + strcpy((char*)dc + dc->name.offset, path); +} + +void Header::addDynamicLinker() +{ + const char* path = "/usr/lib/dyld"; + uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylinker_command) + strlen(path) + 1)); + dylinker_command* dc = (dylinker_command*)appendLoadCommand(LC_LOAD_DYLINKER, alignedSize); + dc->name.offset = sizeof(dylinker_command); + strcpy((char*)dc + dc->name.offset, path); +} + +void Header::addFairPlayEncrypted(uint32_t offset, uint32_t size) +{ + if ( is64() ) { + encryption_info_command_64 en64; + en64.cmd = LC_ENCRYPTION_INFO_64; + en64.cmdsize = sizeof(encryption_info_command_64); + en64.cryptoff = offset; + en64.cryptsize = size; + en64.cryptid = 0; + en64.pad = 0; + appendLoadCommand((load_command*)&en64); + } + else { + encryption_info_command en32; + en32.cmd = LC_ENCRYPTION_INFO; + en32.cmdsize = sizeof(encryption_info_command); + en32.cryptoff = offset; + en32.cryptsize = size; + en32.cryptid = 0; + appendLoadCommand((load_command*)&en32); + } +} + +void Header::addRPath(const char* path) +{ + uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(rpath_command) + strlen(path) + 1)); + rpath_command* rc = (rpath_command*)appendLoadCommand(LC_RPATH, alignedSize); + rc->path.offset = sizeof(rpath_command); + strcpy((char*)rc + rc->path.offset, path); +} + +void Header::addDyldEnvVar(const char* path) +{ + uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylinker_command) + strlen(path) + 1)); + dylinker_command* dc = (dylinker_command*)appendLoadCommand(LC_DYLD_ENVIRONMENT, alignedSize); + dc->name.offset = sizeof(dylinker_command); + strcpy((char*)dc + dc->name.offset, path); +} + +void Header::addAllowableClient(const char* clientName) +{ + uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(sub_client_command) + strlen(clientName) + 1)); + sub_client_command* ac = (sub_client_command*)appendLoadCommand(LC_SUB_CLIENT, alignedSize); + ac->client.offset = sizeof(sub_client_command); + strcpy((char*)ac + ac->client.offset, clientName); +} + +void Header::addUmbrellaName(const char* umbrellaName) +{ + uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(sub_framework_command) + strlen(umbrellaName) + 1)); + sub_framework_command* ac = (sub_framework_command*)appendLoadCommand(LC_SUB_FRAMEWORK, alignedSize); + ac->umbrella.offset = sizeof(sub_framework_command); + strcpy((char*)ac + ac->umbrella.offset, umbrellaName); +} + +void Header::addSourceVersion(Version64 vers) +{ + source_version_command svc; + svc.cmd = LC_SOURCE_VERSION; + svc.cmdsize = sizeof(source_version_command); + svc.version = vers.value(); + appendLoadCommand((load_command*)&svc); +} + +void Header::setMain(uint32_t offset) +{ + entry_point_command ec; + ec.cmd = LC_MAIN; + ec.cmdsize = sizeof(entry_point_command); + ec.entryoff = offset; + ec.stacksize = 0; + appendLoadCommand((load_command*)&ec); +} + +void Header::setCustomStackSize(uint64_t stackSize) { + __block bool found = false; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if (cmd->cmd == LC_MAIN) { + entry_point_command* ec = (entry_point_command*)cmd; + ec->stacksize = stackSize; + found = true; + stop = true; + } + }); + assert(found); +} + +void Header::setUnixEntry(uint64_t startAddr) +{ + // FIXME: support other archs + if ( (mh.cputype == CPU_TYPE_ARM64) || (mh.cputype == CPU_TYPE_ARM64_32) ) { + uint32_t lcSize = 288; + uint32_t* words = (uint32_t*)appendLoadCommand(LC_UNIXTHREAD, lcSize); + words[2] = 6; // flavor = ARM_THREAD_STATE64 + words[3] = 68; // count = ARM_EXCEPTION_STATE64_COUNT + bzero(&words[4], lcSize-16); + *(uint64_t*)(&words[68]) = startAddr; // register pc = startAddr + } + else if ( mh.cputype == CPU_TYPE_X86_64 ) { + uint32_t lcSize = 184; + uint32_t* words = (uint32_t*)appendLoadCommand(LC_UNIXTHREAD, lcSize); + words[2] = 4; // flavor = x86_THREAD_STATE64 + words[3] = 42; // count = x86_THREAD_STATE64_COUNT + bzero(&words[4], lcSize-16); + *(uint64_t*)(&words[36]) = startAddr; // register pc = startAddr + } + else { + assert(0 && "arch not supported"); + } +} + +void Header::addCodeSignature(uint32_t fileOffset, uint32_t fileSize) +{ + linkedit_data_command lc; + lc.cmd = LC_CODE_SIGNATURE; + lc.cmdsize = sizeof(linkedit_data_command); + lc.dataoff = fileOffset; + lc.datasize = fileSize; + appendLoadCommand((load_command*)&lc); +} + +void Header::setBindOpcodesInfo(uint32_t rebaseOffset, uint32_t rebaseSize, + uint32_t bindsOffset, uint32_t bindsSize, + uint32_t weakBindsOffset, uint32_t weakBindsSize, + uint32_t lazyBindsOffset, uint32_t lazyBindsSize, + uint32_t exportTrieOffset, uint32_t exportTrieSize) +{ + dyld_info_command lc; + lc.cmd = LC_DYLD_INFO_ONLY; + lc.cmdsize = sizeof(dyld_info_command); + lc.rebase_off = rebaseOffset; + lc.rebase_size = rebaseSize; + lc.bind_off = bindsOffset; + lc.bind_size = bindsSize; + lc.weak_bind_off = weakBindsOffset; + lc.weak_bind_size = weakBindsSize; + lc.lazy_bind_off = lazyBindsOffset; + lc.lazy_bind_size = lazyBindsSize; + lc.export_off = exportTrieOffset; + lc.export_size = exportTrieSize; + appendLoadCommand((load_command*)&lc); +} + +void Header::setChainedFixupsInfo(uint32_t cfOffset, uint32_t cfSize) +{ + linkedit_data_command lc; + lc.cmd = LC_DYLD_CHAINED_FIXUPS; + lc.cmdsize = sizeof(linkedit_data_command); + lc.dataoff = cfOffset; + lc.datasize = cfSize; + appendLoadCommand((load_command*)&lc); +} + +void Header::setExportTrieInfo(uint32_t offset, uint32_t size) +{ + linkedit_data_command lc; + lc.cmd = LC_DYLD_EXPORTS_TRIE; + lc.cmdsize = sizeof(linkedit_data_command); + lc.dataoff = offset; + lc.datasize = size; + appendLoadCommand((load_command*)&lc); +} + +void Header::setSplitSegInfo(uint32_t offset, uint32_t size) +{ + linkedit_data_command lc; + lc.cmd = LC_SEGMENT_SPLIT_INFO; + lc.cmdsize = sizeof(linkedit_data_command); + lc.dataoff = offset; + lc.datasize = size; + appendLoadCommand((load_command*)&lc); +} + +void Header::setDataInCode(uint32_t offset, uint32_t size) +{ + linkedit_data_command lc; + lc.cmd = LC_DATA_IN_CODE; + lc.cmdsize = sizeof(linkedit_data_command); + lc.dataoff = offset; + lc.datasize = size; + appendLoadCommand((load_command*)&lc); +} + +void Header::setFunctionStarts(uint32_t offset, uint32_t size) +{ + linkedit_data_command lc; + lc.cmd = LC_FUNCTION_STARTS; + lc.cmdsize = sizeof(linkedit_data_command); + lc.dataoff = offset; + lc.datasize = size; + appendLoadCommand((load_command*)&lc); +} + +void Header::setAtomInfo(uint32_t offset, uint32_t size) +{ + linkedit_data_command lc; + lc.cmd = LC_ATOM_INFO; + lc.cmdsize = sizeof(linkedit_data_command); + lc.dataoff = offset; + lc.datasize = size; + appendLoadCommand((load_command*)&lc); +} + +void Header::setSymbolTable(uint32_t nlistOffset, uint32_t nlistCount, uint32_t stringPoolOffset, uint32_t stringPoolSize, + uint32_t localsCount, uint32_t globalsCount, uint32_t undefCount, uint32_t indOffset, uint32_t indCount) +{ + symtab_command stc; + stc.cmd = LC_SYMTAB; + stc.cmdsize = sizeof(symtab_command); + stc.symoff = nlistOffset; + stc.nsyms = nlistCount; + stc.stroff = stringPoolOffset; + stc.strsize = stringPoolSize; + appendLoadCommand((load_command*)&stc); + + dysymtab_command dstc; + bzero(&dstc, sizeof(dstc)); + dstc.cmd = LC_DYSYMTAB; + dstc.cmdsize = sizeof(dysymtab_command); + dstc.ilocalsym = 0; + dstc.nlocalsym = localsCount; + dstc.iextdefsym = localsCount; + dstc.nextdefsym = globalsCount; + dstc.iundefsym = localsCount+globalsCount; + dstc.nundefsym = undefCount; + dstc.indirectsymoff = indOffset; + dstc.nindirectsyms = indCount; + appendLoadCommand((load_command*)&dstc); +} + +void Header::addLinkerOption(std::span buffer, uint32_t count) +{ + uint32_t cmdSize = pointerAligned(sizeof(linker_option_command) + (uint32_t)buffer.size()); + + linker_option_command* lc = (linker_option_command*)appendLoadCommand(LC_LINKER_OPTION, cmdSize); + lc->cmd = LC_LINKER_OPTION; + lc->cmdsize = cmdSize; + lc->count = count; + memcpy((uint8_t*)(lc + 1), buffer.data(), buffer.size()); +} + +Header::LinkerOption Header::LinkerOption::make(std::span opts) +{ + LinkerOption out; + out.count = (uint32_t)opts.size(); + assert(out.count == opts.size()); + for ( CString option : opts ) { + if ( option.empty() ) + continue; + size_t previousSize = out.buffer.size(); + out.buffer.resize(previousSize + option.size() + 1); + option.strcpy((char*)out.buffer.data() + previousSize); + } + return out; +} + +load_command* Header::findLoadCommand(uint32_t cmdNum) +{ + __block load_command* result = nullptr; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == cmdNum ) { + result = (load_command*)cmd; + stop = true; + } + }); + return result; +} + +void Header::removeLoadCommand(void (^callback)(const load_command* cmd, bool& remove, bool& stop)) +{ + bool stop = false; + const load_command* startCmds = nullptr; + if ( mh.magic == MH_MAGIC_64 ) + startCmds = (load_command*)((char*)this + sizeof(mach_header_64)); + else if ( mh.magic == MH_MAGIC ) + startCmds = (load_command*)((char*)this + sizeof(mach_header)); + else if ( hasMachOBigEndianMagic() ) + return; // can't process big endian mach-o + else { + //const uint32_t* h = (uint32_t*)this; + //diag.error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h [1]); + return; // not a mach-o file + } + const load_command* const cmdsEnd = (load_command*)((char*)startCmds + mh.sizeofcmds); + auto cmd = (load_command*)startCmds; + const uint32_t origNcmds = mh.ncmds; + unsigned bytesRemaining = mh.sizeofcmds; + for ( uint32_t i = 0; i < origNcmds; ++i ) { + bool remove = false; + auto nextCmd = (load_command*)((char*)cmd + cmd->cmdsize); + if ( cmd->cmdsize < 8 ) { + //diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) too small", i, mh.ncmds, cmd, this, cmd->cmdsize); + return; + } + if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) { + //diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, mh.ncmds, cmd, this, cmd->cmdsize, cmdsEnd); + return; + } + callback(cmd, remove, stop); + if ( remove ) { + mh.sizeofcmds -= cmd->cmdsize; + ::memmove((void*)cmd, (void*)nextCmd, bytesRemaining); + mh.ncmds--; + } + else { + bytesRemaining -= cmd->cmdsize; + cmd = nextCmd; + } + if ( stop ) + break; + } + if ( cmd ) + ::bzero(cmd, bytesRemaining); +} + +uint32_t Header::relocatableHeaderAndLoadCommandsSize(bool is64, uint32_t sectionCount, uint32_t platformsCount, std::span linkerOptions) +{ + uint32_t size = 0; + if ( is64 ) { + size += sizeof(mach_header_64); + size += sizeof(segment_command_64); + size += sizeof(section_64) * sectionCount; + } + else { + size += sizeof(mach_header); + size += sizeof(segment_command); + size += sizeof(section) * sectionCount; + } + size += sizeof(symtab_command); + size += sizeof(dysymtab_command); + size += sizeof(build_version_command) * platformsCount; + size += sizeof(linkedit_data_command); + + for ( Header::LinkerOption opt : linkerOptions ) { + size += opt.lcSize(); + } + return size; +} + +void Header::setRelocatableSectionCount(uint32_t sectionCount) +{ + assert(mh.filetype == MH_OBJECT); + if ( is64() ) { + uint32_t lcSize = (uint32_t)(sizeof(segment_command_64) + sectionCount * sizeof(section_64)); + segment_command_64* sc = (segment_command_64*)appendLoadCommand(LC_SEGMENT_64, lcSize); + sc->segname[0] = '\0'; // MH_OBJECT has one segment with no name + sc->vmaddr = 0; + sc->vmsize = 0; // adjusted in updateRelocatableSegmentSize() + sc->fileoff = 0; + sc->filesize = 0; // adjusted in updateRelocatableSegmentSize() + sc->initprot = 7; + sc->maxprot = 7; + sc->nsects = sectionCount; + // section info to be filled in later by setRelocatableSectionInfo() + bzero((uint8_t*)sc + sizeof(segment_command_64), sectionCount * sizeof(section_64)); + } + else { + uint32_t lcSize = (uint32_t)(sizeof(segment_command) + sectionCount * sizeof(section)); + segment_command* sc = (segment_command*)appendLoadCommand(LC_SEGMENT, lcSize); + sc->segname[0] = '\0'; // MH_OBJECT has one segment with no name + sc->vmaddr = 0; + sc->vmsize = 0x1000; // FIXME: need dynamic segment layout + sc->fileoff = 0; + sc->filesize = 0x1000; + sc->initprot = 7; + sc->maxprot = 7; + sc->nsects = sectionCount; + // section info to be filled in later by setRelocatableSectionInfo() + bzero((uint8_t*)sc + sizeof(segment_command), sectionCount * sizeof(struct section)); + } +} + +void Header::updateRelocatableSegmentSize(uint64_t vmSize, uint32_t fileSize) +{ + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT ) { + segment_command* sc = (segment_command*)cmd; + sc->vmsize = (uint32_t)vmSize; + sc->filesize = fileSize; + stop = true; + } + else if ( cmd->cmd == LC_SEGMENT_64 ) { + segment_command_64* sc = (segment_command_64*)cmd; + sc->vmsize = vmSize; + sc->filesize = fileSize; + stop = true; + } + }); +} + + +void Header::setRelocatableSectionInfo(uint32_t sectionIndex, const char* segName, const char* sectName, + uint32_t flags, uint64_t address, uint64_t size, uint32_t fileOffset, + uint16_t alignment, uint32_t relocsOffset, uint32_t relocsCount) +{ + __block struct section* section32 = nullptr; + __block struct section_64* section64 = nullptr; + forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { + if ( cmd->cmd == LC_SEGMENT ) { + struct section* sections = (struct section*)((uint8_t*)cmd + sizeof(segment_command)); + section32 = §ions[sectionIndex]; + stop = true; + } + else if ( cmd->cmd == LC_SEGMENT_64 ) { + struct section_64* sections = (struct section_64*)((uint8_t*)cmd + sizeof(segment_command_64)); + section64 = §ions[sectionIndex]; + stop = true; + } + }); + if ( section64 != nullptr ) { + strncpy(section64->segname, segName, 16); + strncpy(section64->sectname, sectName, 16); + section64->addr = address; + section64->size = size; + section64->offset = fileOffset; + section64->align = alignment; + section64->reloff = relocsOffset; + section64->nreloc = relocsCount; + section64->flags = flags; + section64->reserved1 = 0; + section64->reserved2 = 0; + section64->reserved3 = 0; + } + else if ( section32 != nullptr ) { + strncpy(section32->segname, segName, 16); + strncpy(section32->sectname, sectName, 16); + section32->addr = (uint32_t)address; + section32->size = (uint32_t)size; + section32->offset = fileOffset; + section32->align = alignment; + section32->reloff = relocsOffset; + section32->nreloc = relocsCount; + section32->flags = flags; + section32->reserved1 = 0; + section32->reserved2 = 0; + } +} + +#endif // BUILDING_MACHO_WRITER + + +} // namespace dyld3 \ No newline at end of file diff --git a/III. Checksec/mac/SecCode.cpp b/III. Checksec/mac/SecCode.cpp new file mode 100644 index 0000000..9e2c6f4 --- /dev/null +++ b/III. Checksec/mac/SecCode.cpp @@ -0,0 +1,361 @@ +// Source: https://github.com/apple-oss-distributions/Security/blob/ef677c3d667a44e1737c1b0245e9ed04d11c51c1/OSX/libsecurity_codesigning/lib/SecCode.cpp#L314C5-L314C5 +/* + * Copyright (c) 2006-2015 Apple Inc. All Rights Reserved. + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + +// +// SecCode - API frame for SecCode objects. +// +// Note that some SecCode* functions take SecStaticCodeRef arguments in order to +// accept either static or dynamic code references, operating on the respective +// StaticCode. Those functions are in SecStaticCode.cpp, not here, despite their name. +// +#include "cs.h" +#include "Code.h" +#include "cskernel.h" +#include +#include +#include + +using namespace CodeSigning; + + +// +// CFError user info keys +// +const CFStringRef kSecCFErrorArchitecture = CFSTR("SecCSArchitecture"); +const CFStringRef kSecCFErrorPattern = CFSTR("SecCSPattern"); +const CFStringRef kSecCFErrorResourceSeal = CFSTR("SecCSResourceSeal"); +const CFStringRef kSecCFErrorResourceAdded = CFSTR("SecCSResourceAdded"); +const CFStringRef kSecCFErrorResourceAltered = CFSTR("SecCSResourceAltered"); +const CFStringRef kSecCFErrorResourceMissing = CFSTR("SecCSResourceMissing"); +const CFStringRef kSecCFErrorResourceSideband = CFSTR("SecCSResourceHasSidebandData"); +const CFStringRef kSecCFErrorResourceRecursive = CFSTR("SecCSResourceRecursive"); +const CFStringRef kSecCFErrorInfoPlist = CFSTR("SecCSInfoPlist"); +const CFStringRef kSecCFErrorGuestAttributes = CFSTR("SecCSGuestAttributes"); +const CFStringRef kSecCFErrorRequirementSyntax = CFSTR("SecRequirementSyntax"); +const CFStringRef kSecCFErrorPath = CFSTR("SecComponentPath"); + + +// +// CF-standard type code functions +// +CFTypeID SecCodeGetTypeID(void) +{ + BEGIN_CSAPI + return gCFObjects().Code.typeID; + END_CSAPI1(_kCFRuntimeNotATypeID) +} + + +// +// Get a reference to the calling code. +// +OSStatus SecCodeCopySelf(SecCSFlags flags, SecCodeRef *selfRef) +{ + BEGIN_CSAPI + + checkFlags(flags); + CFRef attributes = makeCFMutableDictionary(1, + kSecGuestAttributePid, CFTempNumber(getpid()).get()); + CodeSigning::Required(selfRef) = SecCode::autoLocateGuest(attributes, flags)->handle(false); + + END_CSAPI +} + + +// +// Get the dynamic status of a code. +// +OSStatus SecCodeGetStatus(SecCodeRef codeRef, SecCSFlags flags, SecCodeStatus *status) +{ + BEGIN_CSAPI + + checkFlags(flags); + CodeSigning::Required(status) = SecCode::required(codeRef)->status(); + + END_CSAPI +} + + +// +// Change the dynamic status of a code +// +OSStatus SecCodeSetStatus(SecCodeRef codeRef, SecCodeStatusOperation operation, + CFDictionaryRef arguments, SecCSFlags flags) +{ + BEGIN_CSAPI + + checkFlags(flags); + SecCode::required(codeRef)->status(operation, arguments); + + END_CSAPI +} + + +// +// Get the StaticCode for an Code +// +OSStatus SecCodeCopyStaticCode(SecCodeRef codeRef, SecCSFlags flags, SecStaticCodeRef *staticCodeRef) +{ + BEGIN_CSAPI + + checkFlags(flags, kSecCSUseAllArchitectures); + SecPointer staticCode = SecCode::required(codeRef)->staticCode(); + if (flags & kSecCSUseAllArchitectures) + if (Universal* macho = staticCode->diskRep()->mainExecutableImage()) // Mach-O main executable + if (macho->narrowed()) { + // create a new StaticCode comprising the whole fat file + RefPointer rep = DiskRep::bestGuess(staticCode->diskRep()->mainExecutablePath()); + staticCode = new SecStaticCode(rep); + } + CodeSigning::Required(staticCodeRef) = staticCode ? staticCode->handle() : NULL; + + END_CSAPI +} + + +// +// Get the host for an Code +// +OSStatus SecCodeCopyHost(SecCodeRef guestRef, SecCSFlags flags, SecCodeRef *hostRef) +{ + BEGIN_CSAPI + + checkFlags(flags); + SecPointer host = SecCode::required(guestRef)->host(); + CodeSigning::Required(hostRef) = host ? host->handle() : NULL; + + END_CSAPI +} + + +// +// Find a guest by attribute(s) +// +const CFStringRef kSecGuestAttributeCanonical = CFSTR("canonical"); +const CFStringRef kSecGuestAttributeHash = CFSTR("codedirectory-hash"); +const CFStringRef kSecGuestAttributeMachPort = CFSTR("mach-port"); +const CFStringRef kSecGuestAttributePid = CFSTR("pid"); +const CFStringRef kSecGuestAttributeAudit = CFSTR("audit"); +const CFStringRef kSecGuestAttributeDynamicCode = CFSTR("dynamicCode"); +const CFStringRef kSecGuestAttributeDynamicCodeInfoPlist = CFSTR("dynamicCodeInfoPlist"); +const CFStringRef kSecGuestAttributeArchitecture = CFSTR("architecture"); +const CFStringRef kSecGuestAttributeSubarchitecture = CFSTR("subarchitecture"); + +#if TARGET_OS_OSX +OSStatus SecCodeCopyGuestWithAttributes(SecCodeRef hostRef, + CFDictionaryRef attributes, SecCSFlags flags, SecCodeRef *guestRef) +{ + BEGIN_CSAPI + + checkFlags(flags); + if (hostRef) { + if (SecCode *guest = SecCode::required(hostRef)->locateGuest(attributes)) + CodeSigning::Required(guestRef) = guest->handle(false); + else + return errSecCSNoSuchCode; + } else + CodeSigning::Required(guestRef) = SecCode::autoLocateGuest(attributes, flags)->handle(false); + + END_CSAPI +} + + +// +// Deprecated since 10.6, DO NOT USE. This can be raced. +// Use SecCodeCreateWithAuditToken instead. +// +OSStatus SecCodeCreateWithPID(pid_t pid, SecCSFlags flags, SecCodeRef *processRef) +{ + BEGIN_CSAPI + + checkFlags(flags); + if (SecCode *guest = KernelCode::active()->locateGuest(CFTemp("{%O=%d}", kSecGuestAttributePid, pid))) + CodeSigning::Required(processRef) = guest->handle(false); + else + return errSecCSNoSuchCode; + + END_CSAPI +} + +// +// Shorthand for getting the SecCodeRef for a UNIX process +// +OSStatus SecCodeCreateWithAuditToken(const audit_token_t *audit, + SecCSFlags flags, SecCodeRef *processRef) +{ + BEGIN_CSAPI + + checkFlags(flags); + CFRef auditData = makeCFData(audit, sizeof(audit_token_t)); + if (SecCode *guest = KernelCode::active()->locateGuest(CFTemp("{%O=%O}", kSecGuestAttributeAudit, auditData.get()))) { + CodeSigning::Required(processRef) = guest->handle(false); + } else { + return errSecCSNoSuchCode; + } + + END_CSAPI +} + +OSStatus SecCodeCreateWithXPCMessage(xpc_object_t message, SecCSFlags flags, + SecCodeRef * __nonnull CF_RETURNS_RETAINED target) +{ + BEGIN_CSAPI + + checkFlags(flags); + + if (xpc_get_type(message) != XPC_TYPE_DICTIONARY) { + return errSecCSInvalidObjectRef; + } + + xpc_connection_t connection = xpc_dictionary_get_remote_connection(message); + if (connection == NULL) { + return errSecCSInvalidObjectRef; + } + + audit_token_t t = {0}; + xpc_connection_get_audit_token(connection, &t); + + return SecCodeCreateWithAuditToken(&t, flags, target); + + END_CSAPI +} + +#endif // TARGET_OS_OSX + + +// +// Check validity of an Code +// +OSStatus SecCodeCheckValidity(SecCodeRef codeRef, SecCSFlags flags, + SecRequirementRef requirementRef) +{ + return SecCodeCheckValidityWithErrors(codeRef, flags, requirementRef, NULL); +} + +OSStatus SecCodeCheckValidityWithErrors(SecCodeRef codeRef, SecCSFlags flags, + SecRequirementRef requirementRef, CFErrorRef *errors) +{ + BEGIN_CSAPI + + checkFlags(flags, + kSecCSConsiderExpiration + | kSecCSStrictValidate + | kSecCSStrictValidateStructure + | kSecCSRestrictSidebandData + | kSecCSEnforceRevocationChecks + | kSecCSAllowNetworkAccess + | kSecCSNoNetworkAccess + ); + SecPointer code = SecCode::required(codeRef); + code->checkValidity(flags); + if (const SecRequirement *req = SecRequirement::optional(requirementRef)) + code->staticCode()->validateRequirement(req->requirement(), errSecCSReqFailed); + + END_CSAPI_ERRORS +} + + +// +// Collect suitably laundered information about the code signature of a SecStaticCode +// and return it as a CFDictionary. +// +// This API contracts to return a few pieces of information even for unsigned +// code. This means that a SecStaticCodeRef is usable as a basic indentifier +// (i.e. handle) for any code out there. +// +const CFStringRef kSecCodeInfoCertificates = CFSTR("certificates"); +const CFStringRef kSecCodeInfoChangedFiles = CFSTR("changed-files"); +const CFStringRef kSecCodeInfoCMS = CFSTR("cms"); +const CFStringRef kSecCodeInfoDesignatedRequirement = CFSTR("designated-requirement"); +const CFStringRef kSecCodeInfoEntitlements = CFSTR("entitlements"); +const CFStringRef kSecCodeInfoEntitlementsDict = CFSTR("entitlements-dict"); +const CFStringRef kSecCodeInfoFlags = CFSTR("flags"); +const CFStringRef kSecCodeInfoFormat = CFSTR("format"); +const CFStringRef kSecCodeInfoDigestAlgorithm = CFSTR("digest-algorithm"); +const CFStringRef kSecCodeInfoDigestAlgorithms = CFSTR("digest-algorithms"); +const CFStringRef kSecCodeInfoPlatformIdentifier = CFSTR("platform-identifier"); +const CFStringRef kSecCodeInfoIdentifier = CFSTR("identifier"); +const CFStringRef kSecCodeInfoImplicitDesignatedRequirement = CFSTR("implicit-requirement"); +const CFStringRef kSecCodeInfoDefaultDesignatedLightweightCodeRequirement = CFSTR("default-designated-lwcr"); +const CFStringRef kSecCodeInfoMainExecutable = CFSTR("main-executable"); +const CFStringRef kSecCodeInfoPList = CFSTR("info-plist"); +const CFStringRef kSecCodeInfoRequirements = CFSTR("requirements"); +const CFStringRef kSecCodeInfoRequirementData = CFSTR("requirement-data"); +const CFStringRef kSecCodeInfoSource = CFSTR("source"); +const CFStringRef kSecCodeInfoStatus = CFSTR("status"); +const CFStringRef kSecCodeInfoTeamIdentifier = CFSTR("teamid"); +const CFStringRef kSecCodeInfoTime = CFSTR("signing-time"); +const CFStringRef kSecCodeInfoTimestamp = CFSTR("signing-timestamp"); +const CFStringRef kSecCodeInfoTrust = CFSTR("trust"); +const CFStringRef kSecCodeInfoUnique = CFSTR("unique"); +const CFStringRef kSecCodeInfoCdHashes = CFSTR("cdhashes"); +const CFStringRef kSecCodeInfoCdHashesFull = CFSTR("cdhashes-full"); +const CFStringRef kSecCodeInfoRuntimeVersion = CFSTR("runtime-version"); +const CFStringRef kSecCodeInfoStapledNotarizationTicket = CFSTR("stapled-ticket"); + +const CFStringRef kSecCodeInfoCodeDirectory = CFSTR("CodeDirectory"); +const CFStringRef kSecCodeInfoCodeOffset = CFSTR("CodeOffset"); +const CFStringRef kSecCodeInfoDiskRepInfo = CFSTR("DiskRepInfo"); +const CFStringRef kSecCodeInfoEntitlementsDER = CFSTR("entitlements-DER"); +const CFStringRef kSecCodeInfoResourceDirectory = CFSTR("ResourceDirectory"); +const CFStringRef kSecCodeInfoNotarizationDate = CFSTR("NotarizationDate"); +const CFStringRef kSecCodeInfoCMSDigestHashType = CFSTR("CMSDigestHashType"); +const CFStringRef kSecCodeInfoCMSDigest = CFSTR("CMSDigest"); +const CFStringRef kSecCodeInfoSignatureVersion = CFSTR("SignatureVersion"); +const CFStringRef kSecCodeInfoLaunchConstraintsSelf = CFSTR("LaunchConstraints-self"); +const CFStringRef kSecCodeInfoLaunchConstraintsParent = CFSTR("LaunchConstraints-parent"); +const CFStringRef kSecCodeInfoLaunchConstraintsResponsible = CFSTR("LaunchConstraints-responsible"); +const CFStringRef kSecCodeInfoLibraryConstraints = CFSTR("LibraryConstraints"); + +/* DiskInfoRepInfo types */ +const CFStringRef kSecCodeInfoDiskRepVersionPlatform = CFSTR("VersionPlatform"); +const CFStringRef kSecCodeInfoDiskRepVersionMin = CFSTR("VersionMin"); +const CFStringRef kSecCodeInfoDiskRepVersionSDK = CFSTR("VersionSDK"); +const CFStringRef kSecCodeInfoDiskRepNoLibraryValidation = CFSTR("NoLibraryValidation"); + + +OSStatus SecCodeCopySigningInformation(SecStaticCodeRef codeRef, SecCSFlags flags, + CFDictionaryRef *infoRef) +{ + BEGIN_CSAPI + + checkFlags(flags, + kSecCSInternalInformation + | kSecCSSigningInformation + | kSecCSRequirementInformation + | kSecCSDynamicInformation + | kSecCSContentInformation + | kSecCSSkipResourceDirectory + | kSecCSCalculateCMSDigest); + + SecPointer code = SecStaticCode::requiredStatic(codeRef); + CFRef info = code->signingInformation(flags); + + if (flags & kSecCSDynamicInformation) + if (SecPointer dcode = SecStaticCode::optionalDynamic(codeRef)) + info.take(cfmake("{+%O,%O=%u}", info.get(), kSecCodeInfoStatus, dcode->status())); + + CodeSigning::Required(infoRef) = info.yield(); + + END_CSAPI +} \ No newline at end of file diff --git a/III. Checksec/mac/loader.h b/III. Checksec/mac/loader.h new file mode 100644 index 0000000..7ea6569 --- /dev/null +++ b/III. Checksec/mac/loader.h @@ -0,0 +1,1590 @@ +// Extracted from Xcode 15 Beta 7: /Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/mach-o/loader.h */ +/* + * Copyright (c) 1999-2019 Apple Inc. All Rights Reserved. + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +#ifndef _MACHO_LOADER_H_ +#define _MACHO_LOADER_H_ + +/* + * This file describes the format of mach object files. + */ +#include + +/* + * is needed here for the cpu_type_t and cpu_subtype_t types + * and contains the constants for the possible values of these types. + */ +#include + +/* + * is needed here for the vm_prot_t type and contains the + * constants that are or'ed together for the possible values of this type. + */ +#include + +/* + * is expected to define the flavors of the thread + * states and the structures of those flavors for each machine. + */ +#include +#include + +/* + * The 32-bit mach header appears at the very beginning of the object file for + * 32-bit architectures. + */ +struct mach_header { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ +}; + +/* Constant for the magic field of the mach_header (32-bit architectures) */ +#define MH_MAGIC 0xfeedface /* the mach magic number */ +#define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */ + +/* + * The 64-bit mach header appears at the very beginning of object files for + * 64-bit architectures. + */ +struct mach_header_64 { + uint32_t magic; /* mach magic number identifier */ + cpu_type_t cputype; /* cpu specifier */ + cpu_subtype_t cpusubtype; /* machine specifier */ + uint32_t filetype; /* type of file */ + uint32_t ncmds; /* number of load commands */ + uint32_t sizeofcmds; /* the size of all the load commands */ + uint32_t flags; /* flags */ + uint32_t reserved; /* reserved */ +}; + +/* Constant for the magic field of the mach_header_64 (64-bit architectures) */ +#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */ +#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */ + +/* + * The layout of the file depends on the filetype. For all but the MH_OBJECT + * file type the segments are padded out and aligned on a segment alignment + * boundary for efficient demand pageing. The MH_EXECUTE, MH_FVMLIB, MH_DYLIB, + * MH_DYLINKER and MH_BUNDLE file types also have the headers included as part + * of their first segment. + * + * The file type MH_OBJECT is a compact format intended as output of the + * assembler and input (and possibly output) of the link editor (the .o + * format). All sections are in one unnamed segment with no segment padding. + * This format is used as an executable format when the file is so small the + * segment padding greatly increases its size. + * + * The file type MH_PRELOAD is an executable format intended for things that + * are not executed under the kernel (proms, stand alones, kernels, etc). The + * format can be executed under the kernel but may demand paged it and not + * preload it before execution. + * + * A core file is in MH_CORE format and can be any in an arbritray legal + * Mach-O file. + * + * Constants for the filetype field of the mach_header + */ +#define MH_OBJECT 0x1 /* relocatable object file */ +#define MH_EXECUTE 0x2 /* demand paged executable file */ +#define MH_FVMLIB 0x3 /* fixed VM shared library file */ +#define MH_CORE 0x4 /* core file */ +#define MH_PRELOAD 0x5 /* preloaded executable file */ +#define MH_DYLIB 0x6 /* dynamically bound shared library */ +#define MH_DYLINKER 0x7 /* dynamic link editor */ +#define MH_BUNDLE 0x8 /* dynamically bound bundle file */ +#define MH_DYLIB_STUB 0x9 /* shared library stub for static */ + /* linking only, no section contents */ +#define MH_DSYM 0xa /* companion file with only debug */ + /* sections */ +#define MH_KEXT_BUNDLE 0xb /* x86_64 kexts */ +#define MH_FILESET 0xc /* set of mach-o's */ + +/* Constants for the flags field of the mach_header */ +#define MH_NOUNDEFS 0x1 /* the object file has no undefined + references */ +#define MH_INCRLINK 0x2 /* the object file is the output of an + incremental link against a base file + and can't be link edited again */ +#define MH_DYLDLINK 0x4 /* the object file is input for the + dynamic linker and can't be staticly + link edited again */ +#define MH_BINDATLOAD 0x8 /* the object file's undefined + references are bound by the dynamic + linker when loaded. */ +#define MH_PREBOUND 0x10 /* the file has its dynamic undefined + references prebound. */ +#define MH_SPLIT_SEGS 0x20 /* the file has its read-only and + read-write segments split */ +#define MH_LAZY_INIT 0x40 /* the shared library init routine is + to be run lazily via catching memory + faults to its writeable segments + (obsolete) */ +#define MH_TWOLEVEL 0x80 /* the image is using two-level name + space bindings */ +#define MH_FORCE_FLAT 0x100 /* the executable is forcing all images + to use flat name space bindings */ +#define MH_NOMULTIDEFS 0x200 /* this umbrella guarantees no multiple + defintions of symbols in its + sub-images so the two-level namespace + hints can always be used. */ +#define MH_NOFIXPREBINDING 0x400 /* do not have dyld notify the + prebinding agent about this + executable */ +#define MH_PREBINDABLE 0x800 /* the binary is not prebound but can + have its prebinding redone. only used + when MH_PREBOUND is not set. */ +#define MH_ALLMODSBOUND 0x1000 /* indicates that this binary binds to + all two-level namespace modules of + its dependent libraries. only used + when MH_PREBINDABLE and MH_TWOLEVEL + are both set. */ +#define MH_SUBSECTIONS_VIA_SYMBOLS 0x2000/* safe to divide up the sections into + sub-sections via symbols for dead + code stripping */ +#define MH_CANONICAL 0x4000 /* the binary has been canonicalized + via the unprebind operation */ +#define MH_WEAK_DEFINES 0x8000 /* the final linked image contains + external weak symbols */ +#define MH_BINDS_TO_WEAK 0x10000 /* the final linked image uses + weak symbols */ + +#define MH_ALLOW_STACK_EXECUTION 0x20000/* When this bit is set, all stacks + in the task will be given stack + execution privilege. Only used in + MH_EXECUTE filetypes. */ +#define MH_ROOT_SAFE 0x40000 /* When this bit is set, the binary + declares it is safe for use in + processes with uid zero */ + +#define MH_SETUID_SAFE 0x80000 /* When this bit is set, the binary + declares it is safe for use in + processes when issetugid() is true */ + +#define MH_NO_REEXPORTED_DYLIBS 0x100000 /* When this bit is set on a dylib, + the static linker does not need to + examine dependent dylibs to see + if any are re-exported */ +#define MH_PIE 0x200000 /* When this bit is set, the OS will + load the main executable at a + random address. Only used in + MH_EXECUTE filetypes. */ +#define MH_DEAD_STRIPPABLE_DYLIB 0x400000 /* Only for use on dylibs. When + linking against a dylib that + has this bit set, the static linker + will automatically not create a + LC_LOAD_DYLIB load command to the + dylib if no symbols are being + referenced from the dylib. */ +#define MH_HAS_TLV_DESCRIPTORS 0x800000 /* Contains a section of type + S_THREAD_LOCAL_VARIABLES */ + +#define MH_NO_HEAP_EXECUTION 0x1000000 /* When this bit is set, the OS will + run the main executable with + a non-executable heap even on + platforms (e.g. i386) that don't + require it. Only used in MH_EXECUTE + filetypes. */ + +#define MH_APP_EXTENSION_SAFE 0x02000000 /* The code was linked for use in an + application extension. */ + +#define MH_NLIST_OUTOFSYNC_WITH_DYLDINFO 0x04000000 /* The external symbols + listed in the nlist symbol table do + not include all the symbols listed in + the dyld info. */ + +#define MH_SIM_SUPPORT 0x08000000 /* Allow LC_MIN_VERSION_MACOS and + LC_BUILD_VERSION load commands with + the platforms macOS, iOSMac, + iOSSimulator, tvOSSimulator and + watchOSSimulator. */ + +#define MH_DYLIB_IN_CACHE 0x80000000 /* Only for use on dylibs. When this bit + is set, the dylib is part of the dyld + shared cache, rather than loose in + the filesystem. */ + +/* + * The load commands directly follow the mach_header. The total size of all + * of the commands is given by the sizeofcmds field in the mach_header. All + * load commands must have as their first two fields cmd and cmdsize. The cmd + * field is filled in with a constant for that command type. Each command type + * has a structure specifically for it. The cmdsize field is the size in bytes + * of the particular load command structure plus anything that follows it that + * is a part of the load command (i.e. section structures, strings, etc.). To + * advance to the next load command the cmdsize can be added to the offset or + * pointer of the current load command. The cmdsize for 32-bit architectures + * MUST be a multiple of 4 bytes and for 64-bit architectures MUST be a multiple + * of 8 bytes (these are forever the maximum alignment of any load commands). + * The padded bytes must be zero. All tables in the object file must also + * follow these rules so the file can be memory mapped. Otherwise the pointers + * to these tables will not work well or at all on some machines. With all + * padding zeroed like objects will compare byte for byte. + */ +struct load_command { + uint32_t cmd; /* type of load command */ + uint32_t cmdsize; /* total size of command in bytes */ +}; + +/* + * After MacOS X 10.1 when a new load command is added that is required to be + * understood by the dynamic linker for the image to execute properly the + * LC_REQ_DYLD bit will be or'ed into the load command constant. If the dynamic + * linker sees such a load command it it does not understand will issue a + * "unknown load command required for execution" error and refuse to use the + * image. Other load commands without this bit that are not understood will + * simply be ignored. + */ +#define LC_REQ_DYLD 0x80000000 + +/* Constants for the cmd field of all load commands, the type */ +#define LC_SEGMENT 0x1 /* segment of this file to be mapped */ +#define LC_SYMTAB 0x2 /* link-edit stab symbol table info */ +#define LC_SYMSEG 0x3 /* link-edit gdb symbol table info (obsolete) */ +#define LC_THREAD 0x4 /* thread */ +#define LC_UNIXTHREAD 0x5 /* unix thread (includes a stack) */ +#define LC_LOADFVMLIB 0x6 /* load a specified fixed VM shared library */ +#define LC_IDFVMLIB 0x7 /* fixed VM shared library identification */ +#define LC_IDENT 0x8 /* object identification info (obsolete) */ +#define LC_FVMFILE 0x9 /* fixed VM file inclusion (internal use) */ +#define LC_PREPAGE 0xa /* prepage command (internal use) */ +#define LC_DYSYMTAB 0xb /* dynamic link-edit symbol table info */ +#define LC_LOAD_DYLIB 0xc /* load a dynamically linked shared library */ +#define LC_ID_DYLIB 0xd /* dynamically linked shared lib ident */ +#define LC_LOAD_DYLINKER 0xe /* load a dynamic linker */ +#define LC_ID_DYLINKER 0xf /* dynamic linker identification */ +#define LC_PREBOUND_DYLIB 0x10 /* modules prebound for a dynamically */ + /* linked shared library */ +#define LC_ROUTINES 0x11 /* image routines */ +#define LC_SUB_FRAMEWORK 0x12 /* sub framework */ +#define LC_SUB_UMBRELLA 0x13 /* sub umbrella */ +#define LC_SUB_CLIENT 0x14 /* sub client */ +#define LC_SUB_LIBRARY 0x15 /* sub library */ +#define LC_TWOLEVEL_HINTS 0x16 /* two-level namespace lookup hints */ +#define LC_PREBIND_CKSUM 0x17 /* prebind checksum */ + +/* + * load a dynamically linked shared library that is allowed to be missing + * (all symbols are weak imported). + */ +#define LC_LOAD_WEAK_DYLIB (0x18 | LC_REQ_DYLD) + +#define LC_SEGMENT_64 0x19 /* 64-bit segment of this file to be + mapped */ +#define LC_ROUTINES_64 0x1a /* 64-bit image routines */ +#define LC_UUID 0x1b /* the uuid */ +#define LC_RPATH (0x1c | LC_REQ_DYLD) /* runpath additions */ +#define LC_CODE_SIGNATURE 0x1d /* local of code signature */ +#define LC_SEGMENT_SPLIT_INFO 0x1e /* local of info to split segments */ +#define LC_REEXPORT_DYLIB (0x1f | LC_REQ_DYLD) /* load and re-export dylib */ +#define LC_LAZY_LOAD_DYLIB 0x20 /* delay load of dylib until first use */ +#define LC_ENCRYPTION_INFO 0x21 /* encrypted segment information */ +#define LC_DYLD_INFO 0x22 /* compressed dyld information */ +#define LC_DYLD_INFO_ONLY (0x22|LC_REQ_DYLD) /* compressed dyld information only */ +#define LC_LOAD_UPWARD_DYLIB (0x23 | LC_REQ_DYLD) /* load upward dylib */ +#define LC_VERSION_MIN_MACOSX 0x24 /* build for MacOSX min OS version */ +#define LC_VERSION_MIN_IPHONEOS 0x25 /* build for iPhoneOS min OS version */ +#define LC_FUNCTION_STARTS 0x26 /* compressed table of function start addresses */ +#define LC_DYLD_ENVIRONMENT 0x27 /* string for dyld to treat + like environment variable */ +#define LC_MAIN (0x28|LC_REQ_DYLD) /* replacement for LC_UNIXTHREAD */ +#define LC_DATA_IN_CODE 0x29 /* table of non-instructions in __text */ +#define LC_SOURCE_VERSION 0x2A /* source version used to build binary */ +#define LC_DYLIB_CODE_SIGN_DRS 0x2B /* Code signing DRs copied from linked dylibs */ +#define LC_ENCRYPTION_INFO_64 0x2C /* 64-bit encrypted segment information */ +#define LC_LINKER_OPTION 0x2D /* linker options in MH_OBJECT files */ +#define LC_LINKER_OPTIMIZATION_HINT 0x2E /* optimization hints in MH_OBJECT files */ +#define LC_VERSION_MIN_TVOS 0x2F /* build for AppleTV min OS version */ +#define LC_VERSION_MIN_WATCHOS 0x30 /* build for Watch min OS version */ +#define LC_NOTE 0x31 /* arbitrary data included within a Mach-O file */ +#define LC_BUILD_VERSION 0x32 /* build for platform min OS version */ +#define LC_DYLD_EXPORTS_TRIE (0x33 | LC_REQ_DYLD) /* used with linkedit_data_command, payload is trie */ +#define LC_DYLD_CHAINED_FIXUPS (0x34 | LC_REQ_DYLD) /* used with linkedit_data_command */ +#define LC_FILESET_ENTRY (0x35 | LC_REQ_DYLD) /* used with fileset_entry_command */ + +/* + * A variable length string in a load command is represented by an lc_str + * union. The strings are stored just after the load command structure and + * the offset is from the start of the load command structure. The size + * of the string is reflected in the cmdsize field of the load command. + * Once again any padded bytes to bring the cmdsize field to a multiple + * of 4 bytes must be zero. + */ +union lc_str { + uint32_t offset; /* offset to the string */ +#ifndef __LP64__ + char *ptr; /* pointer to the string */ +#endif +}; + +/* + * The segment load command indicates that a part of this file is to be + * mapped into the task's address space. The size of this segment in memory, + * vmsize, maybe equal to or larger than the amount to map from this file, + * filesize. The file is mapped starting at fileoff to the beginning of + * the segment in memory, vmaddr. The rest of the memory of the segment, + * if any, is allocated zero fill on demand. The segment's maximum virtual + * memory protection and initial virtual memory protection are specified + * by the maxprot and initprot fields. If the segment has sections then the + * section structures directly follow the segment command and their size is + * reflected in cmdsize. + */ +struct segment_command { /* for 32-bit architectures */ + uint32_t cmd; /* LC_SEGMENT */ + uint32_t cmdsize; /* includes sizeof section structs */ + char segname[16]; /* segment name */ + uint32_t vmaddr; /* memory address of this segment */ + uint32_t vmsize; /* memory size of this segment */ + uint32_t fileoff; /* file offset of this segment */ + uint32_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; + +/* + * The 64-bit segment load command indicates that a part of this file is to be + * mapped into a 64-bit task's address space. If the 64-bit segment has + * sections then section_64 structures directly follow the 64-bit segment + * command and their size is reflected in cmdsize. + */ +struct segment_command_64 { /* for 64-bit architectures */ + uint32_t cmd; /* LC_SEGMENT_64 */ + uint32_t cmdsize; /* includes sizeof section_64 structs */ + char segname[16]; /* segment name */ + uint64_t vmaddr; /* memory address of this segment */ + uint64_t vmsize; /* memory size of this segment */ + uint64_t fileoff; /* file offset of this segment */ + uint64_t filesize; /* amount to map from the file */ + vm_prot_t maxprot; /* maximum VM protection */ + vm_prot_t initprot; /* initial VM protection */ + uint32_t nsects; /* number of sections in segment */ + uint32_t flags; /* flags */ +}; + +/* Constants for the flags field of the segment_command */ +#define SG_HIGHVM 0x1 /* the file contents for this segment is for + the high part of the VM space, the low part + is zero filled (for stacks in core files) */ +#define SG_FVMLIB 0x2 /* this segment is the VM that is allocated by + a fixed VM library, for overlap checking in + the link editor */ +#define SG_NORELOC 0x4 /* this segment has nothing that was relocated + in it and nothing relocated to it, that is + it maybe safely replaced without relocation*/ +#define SG_PROTECTED_VERSION_1 0x8 /* This segment is protected. If the + segment starts at file offset 0, the + first page of the segment is not + protected. All other pages of the + segment are protected. */ +#define SG_READ_ONLY 0x10 /* This segment is made read-only after fixups */ + + + +/* + * A segment is made up of zero or more sections. Non-MH_OBJECT files have + * all of their segments with the proper sections in each, and padded to the + * specified segment alignment when produced by the link editor. The first + * segment of a MH_EXECUTE and MH_FVMLIB format file contains the mach_header + * and load commands of the object file before its first section. The zero + * fill sections are always last in their segment (in all formats). This + * allows the zeroed segment padding to be mapped into memory where zero fill + * sections might be. The gigabyte zero fill sections, those with the section + * type S_GB_ZEROFILL, can only be in a segment with sections of this type. + * These segments are then placed after all other segments. + * + * The MH_OBJECT format has all of its sections in one segment for + * compactness. There is no padding to a specified segment boundary and the + * mach_header and load commands are not part of the segment. + * + * Sections with the same section name, sectname, going into the same segment, + * segname, are combined by the link editor. The resulting section is aligned + * to the maximum alignment of the combined sections and is the new section's + * alignment. The combined sections are aligned to their original alignment in + * the combined section. Any padded bytes to get the specified alignment are + * zeroed. + * + * The format of the relocation entries referenced by the reloff and nreloc + * fields of the section structure for mach object files is described in the + * header file . + */ +struct section { /* for 32-bit architectures */ + char sectname[16]; /* name of this section */ + char segname[16]; /* segment this section goes in */ + uint32_t addr; /* memory address of this section */ + uint32_t size; /* size in bytes of this section */ + uint32_t offset; /* file offset of this section */ + uint32_t align; /* section alignment (power of 2) */ + uint32_t reloff; /* file offset of relocation entries */ + uint32_t nreloc; /* number of relocation entries */ + uint32_t flags; /* flags (section type and attributes)*/ + uint32_t reserved1; /* reserved (for offset or index) */ + uint32_t reserved2; /* reserved (for count or sizeof) */ +}; + +struct section_64 { /* for 64-bit architectures */ + char sectname[16]; /* name of this section */ + char segname[16]; /* segment this section goes in */ + uint64_t addr; /* memory address of this section */ + uint64_t size; /* size in bytes of this section */ + uint32_t offset; /* file offset of this section */ + uint32_t align; /* section alignment (power of 2) */ + uint32_t reloff; /* file offset of relocation entries */ + uint32_t nreloc; /* number of relocation entries */ + uint32_t flags; /* flags (section type and attributes)*/ + uint32_t reserved1; /* reserved (for offset or index) */ + uint32_t reserved2; /* reserved (for count or sizeof) */ + uint32_t reserved3; /* reserved */ +}; + +/* + * The flags field of a section structure is separated into two parts a section + * type and section attributes. The section types are mutually exclusive (it + * can only have one type) but the section attributes are not (it may have more + * than one attribute). + */ +#define SECTION_TYPE 0x000000ff /* 256 section types */ +#define SECTION_ATTRIBUTES 0xffffff00 /* 24 section attributes */ + +/* Constants for the type of a section */ +#define S_REGULAR 0x0 /* regular section */ +#define S_ZEROFILL 0x1 /* zero fill on demand section */ +#define S_CSTRING_LITERALS 0x2 /* section with only literal C strings*/ +#define S_4BYTE_LITERALS 0x3 /* section with only 4 byte literals */ +#define S_8BYTE_LITERALS 0x4 /* section with only 8 byte literals */ +#define S_LITERAL_POINTERS 0x5 /* section with only pointers to */ + /* literals */ +/* + * For the two types of symbol pointers sections and the symbol stubs section + * they have indirect symbol table entries. For each of the entries in the + * section the indirect symbol table entries, in corresponding order in the + * indirect symbol table, start at the index stored in the reserved1 field + * of the section structure. Since the indirect symbol table entries + * correspond to the entries in the section the number of indirect symbol table + * entries is inferred from the size of the section divided by the size of the + * entries in the section. For symbol pointers sections the size of the entries + * in the section is 4 bytes and for symbol stubs sections the byte size of the + * stubs is stored in the reserved2 field of the section structure. + */ +#define S_NON_LAZY_SYMBOL_POINTERS 0x6 /* section with only non-lazy + symbol pointers */ +#define S_LAZY_SYMBOL_POINTERS 0x7 /* section with only lazy symbol + pointers */ +#define S_SYMBOL_STUBS 0x8 /* section with only symbol + stubs, byte size of stub in + the reserved2 field */ +#define S_MOD_INIT_FUNC_POINTERS 0x9 /* section with only function + pointers for initialization*/ +#define S_MOD_TERM_FUNC_POINTERS 0xa /* section with only function + pointers for termination */ +#define S_COALESCED 0xb /* section contains symbols that + are to be coalesced */ +#define S_GB_ZEROFILL 0xc /* zero fill on demand section + (that can be larger than 4 + gigabytes) */ +#define S_INTERPOSING 0xd /* section with only pairs of + function pointers for + interposing */ +#define S_16BYTE_LITERALS 0xe /* section with only 16 byte + literals */ +#define S_DTRACE_DOF 0xf /* section contains + DTrace Object Format */ +#define S_LAZY_DYLIB_SYMBOL_POINTERS 0x10 /* section with only lazy + symbol pointers to lazy + loaded dylibs */ +/* + * Section types to support thread local variables + */ +#define S_THREAD_LOCAL_REGULAR 0x11 /* template of initial + values for TLVs */ +#define S_THREAD_LOCAL_ZEROFILL 0x12 /* template of initial + values for TLVs */ +#define S_THREAD_LOCAL_VARIABLES 0x13 /* TLV descriptors */ +#define S_THREAD_LOCAL_VARIABLE_POINTERS 0x14 /* pointers to TLV + descriptors */ +#define S_THREAD_LOCAL_INIT_FUNCTION_POINTERS 0x15 /* functions to call + to initialize TLV + values */ +#define S_INIT_FUNC_OFFSETS 0x16 /* 32-bit offsets to + initializers */ + +/* + * Constants for the section attributes part of the flags field of a section + * structure. + */ +#define SECTION_ATTRIBUTES_USR 0xff000000 /* User setable attributes */ +#define S_ATTR_PURE_INSTRUCTIONS 0x80000000 /* section contains only true + machine instructions */ +#define S_ATTR_NO_TOC 0x40000000 /* section contains coalesced + symbols that are not to be + in a ranlib table of + contents */ +#define S_ATTR_STRIP_STATIC_SYMS 0x20000000 /* ok to strip static symbols + in this section in files + with the MH_DYLDLINK flag */ +#define S_ATTR_NO_DEAD_STRIP 0x10000000 /* no dead stripping */ +#define S_ATTR_LIVE_SUPPORT 0x08000000 /* blocks are live if they + reference live blocks */ +#define S_ATTR_SELF_MODIFYING_CODE 0x04000000 /* Used with i386 code stubs + written on by dyld */ +/* + * If a segment contains any sections marked with S_ATTR_DEBUG then all + * sections in that segment must have this attribute. No section other than + * a section marked with this attribute may reference the contents of this + * section. A section with this attribute may contain no symbols and must have + * a section type S_REGULAR. The static linker will not copy section contents + * from sections with this attribute into its output file. These sections + * generally contain DWARF debugging info. + */ +#define S_ATTR_DEBUG 0x02000000 /* a debug section */ +#define SECTION_ATTRIBUTES_SYS 0x00ffff00 /* system setable attributes */ +#define S_ATTR_SOME_INSTRUCTIONS 0x00000400 /* section contains some + machine instructions */ +#define S_ATTR_EXT_RELOC 0x00000200 /* section has external + relocation entries */ +#define S_ATTR_LOC_RELOC 0x00000100 /* section has local + relocation entries */ + + +/* + * The names of segments and sections in them are mostly meaningless to the + * link-editor. But there are few things to support traditional UNIX + * executables that require the link-editor and assembler to use some names + * agreed upon by convention. + * + * The initial protection of the "__TEXT" segment has write protection turned + * off (not writeable). + * + * The link-editor will allocate common symbols at the end of the "__common" + * section in the "__DATA" segment. It will create the section and segment + * if needed. + */ + +/* The currently known segment names and the section names in those segments */ + +#define SEG_PAGEZERO "__PAGEZERO" /* the pagezero segment which has no */ + /* protections and catches NULL */ + /* references for MH_EXECUTE files */ + + +#define SEG_TEXT "__TEXT" /* the tradition UNIX text segment */ +#define SECT_TEXT "__text" /* the real text part of the text */ + /* section no headers, and no padding */ +#define SECT_FVMLIB_INIT0 "__fvmlib_init0" /* the fvmlib initialization */ + /* section */ +#define SECT_FVMLIB_INIT1 "__fvmlib_init1" /* the section following the */ + /* fvmlib initialization */ + /* section */ + +#define SEG_DATA "__DATA" /* the tradition UNIX data segment */ +#define SECT_DATA "__data" /* the real initialized data section */ + /* no padding, no bss overlap */ +#define SECT_BSS "__bss" /* the real uninitialized data section*/ + /* no padding */ +#define SECT_COMMON "__common" /* the section common symbols are */ + /* allocated in by the link editor */ + +#define SEG_OBJC "__OBJC" /* objective-C runtime segment */ +#define SECT_OBJC_SYMBOLS "__symbol_table" /* symbol table */ +#define SECT_OBJC_MODULES "__module_info" /* module information */ +#define SECT_OBJC_STRINGS "__selector_strs" /* string table */ +#define SECT_OBJC_REFS "__selector_refs" /* string table */ + +#define SEG_ICON "__ICON" /* the icon segment */ +#define SECT_ICON_HEADER "__header" /* the icon headers */ +#define SECT_ICON_TIFF "__tiff" /* the icons in tiff format */ + +#define SEG_LINKEDIT "__LINKEDIT" /* the segment containing all structs */ + /* created and maintained by the link */ + /* editor. Created with -seglinkedit */ + /* option to ld(1) for MH_EXECUTE and */ + /* FVMLIB file types only */ + +#define SEG_LINKINFO "__LINKINFO" /* the segment overlapping with linkedit */ + /* containing linking information */ + +#define SEG_UNIXSTACK "__UNIXSTACK" /* the unix stack segment */ + +#define SEG_IMPORT "__IMPORT" /* the segment for the self (dyld) */ + /* modifing code stubs that has read, */ + /* write and execute permissions */ + +/* + * Fixed virtual memory shared libraries are identified by two things. The + * target pathname (the name of the library as found for execution), and the + * minor version number. The address of where the headers are loaded is in + * header_addr. (THIS IS OBSOLETE and no longer supported). + */ +struct fvmlib { + union lc_str name; /* library's target pathname */ + uint32_t minor_version; /* library's minor version number */ + uint32_t header_addr; /* library's header address */ +}; + +/* + * A fixed virtual shared library (filetype == MH_FVMLIB in the mach header) + * contains a fvmlib_command (cmd == LC_IDFVMLIB) to identify the library. + * An object that uses a fixed virtual shared library also contains a + * fvmlib_command (cmd == LC_LOADFVMLIB) for each library it uses. + * (THIS IS OBSOLETE and no longer supported). + */ +struct fvmlib_command { + uint32_t cmd; /* LC_IDFVMLIB or LC_LOADFVMLIB */ + uint32_t cmdsize; /* includes pathname string */ + struct fvmlib fvmlib; /* the library identification */ +}; + +/* + * Dynamicly linked shared libraries are identified by two things. The + * pathname (the name of the library as found for execution), and the + * compatibility version number. The pathname must match and the compatibility + * number in the user of the library must be greater than or equal to the + * library being used. The time stamp is used to record the time a library was + * built and copied into user so it can be use to determined if the library used + * at runtime is exactly the same as used to built the program. + */ +struct dylib { + union lc_str name; /* library's path name */ + uint32_t timestamp; /* library's build time stamp */ + uint32_t current_version; /* library's current version number */ + uint32_t compatibility_version; /* library's compatibility vers number*/ +}; + +/* + * A dynamically linked shared library (filetype == MH_DYLIB in the mach header) + * contains a dylib_command (cmd == LC_ID_DYLIB) to identify the library. + * An object that uses a dynamically linked shared library also contains a + * dylib_command (cmd == LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB, or + * LC_REEXPORT_DYLIB) for each library it uses. + */ +struct dylib_command { + uint32_t cmd; /* LC_ID_DYLIB, LC_LOAD_{,WEAK_}DYLIB, + LC_REEXPORT_DYLIB */ + uint32_t cmdsize; /* includes pathname string */ + struct dylib dylib; /* the library identification */ +}; + +/* + * A dynamically linked shared library may be a subframework of an umbrella + * framework. If so it will be linked with "-umbrella umbrella_name" where + * Where "umbrella_name" is the name of the umbrella framework. A subframework + * can only be linked against by its umbrella framework or other subframeworks + * that are part of the same umbrella framework. Otherwise the static link + * editor produces an error and states to link against the umbrella framework. + * The name of the umbrella framework for subframeworks is recorded in the + * following structure. + */ +struct sub_framework_command { + uint32_t cmd; /* LC_SUB_FRAMEWORK */ + uint32_t cmdsize; /* includes umbrella string */ + union lc_str umbrella; /* the umbrella framework name */ +}; + +/* + * For dynamically linked shared libraries that are subframework of an umbrella + * framework they can allow clients other than the umbrella framework or other + * subframeworks in the same umbrella framework. To do this the subframework + * is built with "-allowable_client client_name" and an LC_SUB_CLIENT load + * command is created for each -allowable_client flag. The client_name is + * usually a framework name. It can also be a name used for bundles clients + * where the bundle is built with "-client_name client_name". + */ +struct sub_client_command { + uint32_t cmd; /* LC_SUB_CLIENT */ + uint32_t cmdsize; /* includes client string */ + union lc_str client; /* the client name */ +}; + +/* + * A dynamically linked shared library may be a sub_umbrella of an umbrella + * framework. If so it will be linked with "-sub_umbrella umbrella_name" where + * Where "umbrella_name" is the name of the sub_umbrella framework. When + * staticly linking when -twolevel_namespace is in effect a twolevel namespace + * umbrella framework will only cause its subframeworks and those frameworks + * listed as sub_umbrella frameworks to be implicited linked in. Any other + * dependent dynamic libraries will not be linked it when -twolevel_namespace + * is in effect. The primary library recorded by the static linker when + * resolving a symbol in these libraries will be the umbrella framework. + * Zero or more sub_umbrella frameworks may be use by an umbrella framework. + * The name of a sub_umbrella framework is recorded in the following structure. + */ +struct sub_umbrella_command { + uint32_t cmd; /* LC_SUB_UMBRELLA */ + uint32_t cmdsize; /* includes sub_umbrella string */ + union lc_str sub_umbrella; /* the sub_umbrella framework name */ +}; + +/* + * A dynamically linked shared library may be a sub_library of another shared + * library. If so it will be linked with "-sub_library library_name" where + * Where "library_name" is the name of the sub_library shared library. When + * staticly linking when -twolevel_namespace is in effect a twolevel namespace + * shared library will only cause its subframeworks and those frameworks + * listed as sub_umbrella frameworks and libraries listed as sub_libraries to + * be implicited linked in. Any other dependent dynamic libraries will not be + * linked it when -twolevel_namespace is in effect. The primary library + * recorded by the static linker when resolving a symbol in these libraries + * will be the umbrella framework (or dynamic library). Zero or more sub_library + * shared libraries may be use by an umbrella framework or (or dynamic library). + * The name of a sub_library framework is recorded in the following structure. + * For example /usr/lib/libobjc_profile.A.dylib would be recorded as "libobjc". + */ +struct sub_library_command { + uint32_t cmd; /* LC_SUB_LIBRARY */ + uint32_t cmdsize; /* includes sub_library string */ + union lc_str sub_library; /* the sub_library name */ +}; + +/* + * A program (filetype == MH_EXECUTE) that is + * prebound to its dynamic libraries has one of these for each library that + * the static linker used in prebinding. It contains a bit vector for the + * modules in the library. The bits indicate which modules are bound (1) and + * which are not (0) from the library. The bit for module 0 is the low bit + * of the first byte. So the bit for the Nth module is: + * (linked_modules[N/8] >> N%8) & 1 + */ +struct prebound_dylib_command { + uint32_t cmd; /* LC_PREBOUND_DYLIB */ + uint32_t cmdsize; /* includes strings */ + union lc_str name; /* library's path name */ + uint32_t nmodules; /* number of modules in library */ + union lc_str linked_modules; /* bit vector of linked modules */ +}; + +/* + * A program that uses a dynamic linker contains a dylinker_command to identify + * the name of the dynamic linker (LC_LOAD_DYLINKER). And a dynamic linker + * contains a dylinker_command to identify the dynamic linker (LC_ID_DYLINKER). + * A file can have at most one of these. + * This struct is also used for the LC_DYLD_ENVIRONMENT load command and + * contains string for dyld to treat like environment variable. + */ +struct dylinker_command { + uint32_t cmd; /* LC_ID_DYLINKER, LC_LOAD_DYLINKER or + LC_DYLD_ENVIRONMENT */ + uint32_t cmdsize; /* includes pathname string */ + union lc_str name; /* dynamic linker's path name */ +}; + +/* + * Thread commands contain machine-specific data structures suitable for + * use in the thread state primitives. The machine specific data structures + * follow the struct thread_command as follows. + * Each flavor of machine specific data structure is preceded by an uint32_t + * constant for the flavor of that data structure, an uint32_t that is the + * count of uint32_t's of the size of the state data structure and then + * the state data structure follows. This triple may be repeated for many + * flavors. The constants for the flavors, counts and state data structure + * definitions are expected to be in the header file . + * These machine specific data structures sizes must be multiples of + * 4 bytes. The cmdsize reflects the total size of the thread_command + * and all of the sizes of the constants for the flavors, counts and state + * data structures. + * + * For executable objects that are unix processes there will be one + * thread_command (cmd == LC_UNIXTHREAD) created for it by the link-editor. + * This is the same as a LC_THREAD, except that a stack is automatically + * created (based on the shell's limit for the stack size). Command arguments + * and environment variables are copied onto that stack. + */ +struct thread_command { + uint32_t cmd; /* LC_THREAD or LC_UNIXTHREAD */ + uint32_t cmdsize; /* total size of this command */ + /* uint32_t flavor flavor of thread state */ + /* uint32_t count count of uint32_t's in thread state */ + /* struct XXX_thread_state state thread state for this flavor */ + /* ... */ +}; + +/* + * The routines command contains the address of the dynamic shared library + * initialization routine and an index into the module table for the module + * that defines the routine. Before any modules are used from the library the + * dynamic linker fully binds the module that defines the initialization routine + * and then calls it. This gets called before any module initialization + * routines (used for C++ static constructors) in the library. + */ +struct routines_command { /* for 32-bit architectures */ + uint32_t cmd; /* LC_ROUTINES */ + uint32_t cmdsize; /* total size of this command */ + uint32_t init_address; /* address of initialization routine */ + uint32_t init_module; /* index into the module table that */ + /* the init routine is defined in */ + uint32_t reserved1; + uint32_t reserved2; + uint32_t reserved3; + uint32_t reserved4; + uint32_t reserved5; + uint32_t reserved6; +}; + +/* + * The 64-bit routines command. Same use as above. + */ +struct routines_command_64 { /* for 64-bit architectures */ + uint32_t cmd; /* LC_ROUTINES_64 */ + uint32_t cmdsize; /* total size of this command */ + uint64_t init_address; /* address of initialization routine */ + uint64_t init_module; /* index into the module table that */ + /* the init routine is defined in */ + uint64_t reserved1; + uint64_t reserved2; + uint64_t reserved3; + uint64_t reserved4; + uint64_t reserved5; + uint64_t reserved6; +}; + +/* + * The symtab_command contains the offsets and sizes of the link-edit 4.3BSD + * "stab" style symbol table information as described in the header files + * and . + */ +struct symtab_command { + uint32_t cmd; /* LC_SYMTAB */ + uint32_t cmdsize; /* sizeof(struct symtab_command) */ + uint32_t symoff; /* symbol table offset */ + uint32_t nsyms; /* number of symbol table entries */ + uint32_t stroff; /* string table offset */ + uint32_t strsize; /* string table size in bytes */ +}; + +/* + * This is the second set of the symbolic information which is used to support + * the data structures for the dynamically link editor. + * + * The original set of symbolic information in the symtab_command which contains + * the symbol and string tables must also be present when this load command is + * present. When this load command is present the symbol table is organized + * into three groups of symbols: + * local symbols (static and debugging symbols) - grouped by module + * defined external symbols - grouped by module (sorted by name if not lib) + * undefined external symbols (sorted by name if MH_BINDATLOAD is not set, + * and in order the were seen by the static + * linker if MH_BINDATLOAD is set) + * In this load command there are offsets and counts to each of the three groups + * of symbols. + * + * This load command contains a the offsets and sizes of the following new + * symbolic information tables: + * table of contents + * module table + * reference symbol table + * indirect symbol table + * The first three tables above (the table of contents, module table and + * reference symbol table) are only present if the file is a dynamically linked + * shared library. For executable and object modules, which are files + * containing only one module, the information that would be in these three + * tables is determined as follows: + * table of contents - the defined external symbols are sorted by name + * module table - the file contains only one module so everything in the + * file is part of the module. + * reference symbol table - is the defined and undefined external symbols + * + * For dynamically linked shared library files this load command also contains + * offsets and sizes to the pool of relocation entries for all sections + * separated into two groups: + * external relocation entries + * local relocation entries + * For executable and object modules the relocation entries continue to hang + * off the section structures. + */ +struct dysymtab_command { + uint32_t cmd; /* LC_DYSYMTAB */ + uint32_t cmdsize; /* sizeof(struct dysymtab_command) */ + + /* + * The symbols indicated by symoff and nsyms of the LC_SYMTAB load command + * are grouped into the following three groups: + * local symbols (further grouped by the module they are from) + * defined external symbols (further grouped by the module they are from) + * undefined symbols + * + * The local symbols are used only for debugging. The dynamic binding + * process may have to use them to indicate to the debugger the local + * symbols for a module that is being bound. + * + * The last two groups are used by the dynamic binding process to do the + * binding (indirectly through the module table and the reference symbol + * table when this is a dynamically linked shared library file). + */ + uint32_t ilocalsym; /* index to local symbols */ + uint32_t nlocalsym; /* number of local symbols */ + + uint32_t iextdefsym;/* index to externally defined symbols */ + uint32_t nextdefsym;/* number of externally defined symbols */ + + uint32_t iundefsym; /* index to undefined symbols */ + uint32_t nundefsym; /* number of undefined symbols */ + + /* + * For the for the dynamic binding process to find which module a symbol + * is defined in the table of contents is used (analogous to the ranlib + * structure in an archive) which maps defined external symbols to modules + * they are defined in. This exists only in a dynamically linked shared + * library file. For executable and object modules the defined external + * symbols are sorted by name and is use as the table of contents. + */ + uint32_t tocoff; /* file offset to table of contents */ + uint32_t ntoc; /* number of entries in table of contents */ + + /* + * To support dynamic binding of "modules" (whole object files) the symbol + * table must reflect the modules that the file was created from. This is + * done by having a module table that has indexes and counts into the merged + * tables for each module. The module structure that these two entries + * refer to is described below. This exists only in a dynamically linked + * shared library file. For executable and object modules the file only + * contains one module so everything in the file belongs to the module. + */ + uint32_t modtaboff; /* file offset to module table */ + uint32_t nmodtab; /* number of module table entries */ + + /* + * To support dynamic module binding the module structure for each module + * indicates the external references (defined and undefined) each module + * makes. For each module there is an offset and a count into the + * reference symbol table for the symbols that the module references. + * This exists only in a dynamically linked shared library file. For + * executable and object modules the defined external symbols and the + * undefined external symbols indicates the external references. + */ + uint32_t extrefsymoff; /* offset to referenced symbol table */ + uint32_t nextrefsyms; /* number of referenced symbol table entries */ + + /* + * The sections that contain "symbol pointers" and "routine stubs" have + * indexes and (implied counts based on the size of the section and fixed + * size of the entry) into the "indirect symbol" table for each pointer + * and stub. For every section of these two types the index into the + * indirect symbol table is stored in the section header in the field + * reserved1. An indirect symbol table entry is simply a 32bit index into + * the symbol table to the symbol that the pointer or stub is referring to. + * The indirect symbol table is ordered to match the entries in the section. + */ + uint32_t indirectsymoff; /* file offset to the indirect symbol table */ + uint32_t nindirectsyms; /* number of indirect symbol table entries */ + + /* + * To support relocating an individual module in a library file quickly the + * external relocation entries for each module in the library need to be + * accessed efficiently. Since the relocation entries can't be accessed + * through the section headers for a library file they are separated into + * groups of local and external entries further grouped by module. In this + * case the presents of this load command who's extreloff, nextrel, + * locreloff and nlocrel fields are non-zero indicates that the relocation + * entries of non-merged sections are not referenced through the section + * structures (and the reloff and nreloc fields in the section headers are + * set to zero). + * + * Since the relocation entries are not accessed through the section headers + * this requires the r_address field to be something other than a section + * offset to identify the item to be relocated. In this case r_address is + * set to the offset from the vmaddr of the first LC_SEGMENT command. + * For MH_SPLIT_SEGS images r_address is set to the the offset from the + * vmaddr of the first read-write LC_SEGMENT command. + * + * The relocation entries are grouped by module and the module table + * entries have indexes and counts into them for the group of external + * relocation entries for that the module. + * + * For sections that are merged across modules there must not be any + * remaining external relocation entries for them (for merged sections + * remaining relocation entries must be local). + */ + uint32_t extreloff; /* offset to external relocation entries */ + uint32_t nextrel; /* number of external relocation entries */ + + /* + * All the local relocation entries are grouped together (they are not + * grouped by their module since they are only used if the object is moved + * from it staticly link edited address). + */ + uint32_t locreloff; /* offset to local relocation entries */ + uint32_t nlocrel; /* number of local relocation entries */ + +}; + +/* + * An indirect symbol table entry is simply a 32bit index into the symbol table + * to the symbol that the pointer or stub is refering to. Unless it is for a + * non-lazy symbol pointer section for a defined symbol which strip(1) as + * removed. In which case it has the value INDIRECT_SYMBOL_LOCAL. If the + * symbol was also absolute INDIRECT_SYMBOL_ABS is or'ed with that. + */ +#define INDIRECT_SYMBOL_LOCAL 0x80000000 +#define INDIRECT_SYMBOL_ABS 0x40000000 + + +/* a table of contents entry */ +struct dylib_table_of_contents { + uint32_t symbol_index; /* the defined external symbol + (index into the symbol table) */ + uint32_t module_index; /* index into the module table this symbol + is defined in */ +}; + +/* a module table entry */ +struct dylib_module { + uint32_t module_name; /* the module name (index into string table) */ + + uint32_t iextdefsym; /* index into externally defined symbols */ + uint32_t nextdefsym; /* number of externally defined symbols */ + uint32_t irefsym; /* index into reference symbol table */ + uint32_t nrefsym; /* number of reference symbol table entries */ + uint32_t ilocalsym; /* index into symbols for local symbols */ + uint32_t nlocalsym; /* number of local symbols */ + + uint32_t iextrel; /* index into external relocation entries */ + uint32_t nextrel; /* number of external relocation entries */ + + uint32_t iinit_iterm; /* low 16 bits are the index into the init + section, high 16 bits are the index into + the term section */ + uint32_t ninit_nterm; /* low 16 bits are the number of init section + entries, high 16 bits are the number of + term section entries */ + + uint32_t /* for this module address of the start of */ + objc_module_info_addr; /* the (__OBJC,__module_info) section */ + uint32_t /* for this module size of */ + objc_module_info_size; /* the (__OBJC,__module_info) section */ +}; + +/* a 64-bit module table entry */ +struct dylib_module_64 { + uint32_t module_name; /* the module name (index into string table) */ + + uint32_t iextdefsym; /* index into externally defined symbols */ + uint32_t nextdefsym; /* number of externally defined symbols */ + uint32_t irefsym; /* index into reference symbol table */ + uint32_t nrefsym; /* number of reference symbol table entries */ + uint32_t ilocalsym; /* index into symbols for local symbols */ + uint32_t nlocalsym; /* number of local symbols */ + + uint32_t iextrel; /* index into external relocation entries */ + uint32_t nextrel; /* number of external relocation entries */ + + uint32_t iinit_iterm; /* low 16 bits are the index into the init + section, high 16 bits are the index into + the term section */ + uint32_t ninit_nterm; /* low 16 bits are the number of init section + entries, high 16 bits are the number of + term section entries */ + + uint32_t /* for this module size of */ + objc_module_info_size; /* the (__OBJC,__module_info) section */ + uint64_t /* for this module address of the start of */ + objc_module_info_addr; /* the (__OBJC,__module_info) section */ +}; + +/* + * The entries in the reference symbol table are used when loading the module + * (both by the static and dynamic link editors) and if the module is unloaded + * or replaced. Therefore all external symbols (defined and undefined) are + * listed in the module's reference table. The flags describe the type of + * reference that is being made. The constants for the flags are defined in + * as they are also used for symbol table entries. + */ +struct dylib_reference { + uint32_t isym:24, /* index into the symbol table */ + flags:8; /* flags to indicate the type of reference */ +}; + +/* + * The twolevel_hints_command contains the offset and number of hints in the + * two-level namespace lookup hints table. + */ +struct twolevel_hints_command { + uint32_t cmd; /* LC_TWOLEVEL_HINTS */ + uint32_t cmdsize; /* sizeof(struct twolevel_hints_command) */ + uint32_t offset; /* offset to the hint table */ + uint32_t nhints; /* number of hints in the hint table */ +}; + +/* + * The entries in the two-level namespace lookup hints table are twolevel_hint + * structs. These provide hints to the dynamic link editor where to start + * looking for an undefined symbol in a two-level namespace image. The + * isub_image field is an index into the sub-images (sub-frameworks and + * sub-umbrellas list) that made up the two-level image that the undefined + * symbol was found in when it was built by the static link editor. If + * isub-image is 0 the the symbol is expected to be defined in library and not + * in the sub-images. If isub-image is non-zero it is an index into the array + * of sub-images for the umbrella with the first index in the sub-images being + * 1. The array of sub-images is the ordered list of sub-images of the umbrella + * that would be searched for a symbol that has the umbrella recorded as its + * primary library. The table of contents index is an index into the + * library's table of contents. This is used as the starting point of the + * binary search or a directed linear search. + */ +struct twolevel_hint { + uint32_t + isub_image:8, /* index into the sub images */ + itoc:24; /* index into the table of contents */ +}; + +/* + * The prebind_cksum_command contains the value of the original check sum for + * prebound files or zero. When a prebound file is first created or modified + * for other than updating its prebinding information the value of the check sum + * is set to zero. When the file has it prebinding re-done and if the value of + * the check sum is zero the original check sum is calculated and stored in + * cksum field of this load command in the output file. If when the prebinding + * is re-done and the cksum field is non-zero it is left unchanged from the + * input file. + */ +struct prebind_cksum_command { + uint32_t cmd; /* LC_PREBIND_CKSUM */ + uint32_t cmdsize; /* sizeof(struct prebind_cksum_command) */ + uint32_t cksum; /* the check sum or zero */ +}; + +/* + * The uuid load command contains a single 128-bit unique random number that + * identifies an object produced by the static link editor. + */ +struct uuid_command { + uint32_t cmd; /* LC_UUID */ + uint32_t cmdsize; /* sizeof(struct uuid_command) */ + uint8_t uuid[16]; /* the 128-bit uuid */ +}; + +/* + * The rpath_command contains a path which at runtime should be added to + * the current run path used to find @rpath prefixed dylibs. + */ +struct rpath_command { + uint32_t cmd; /* LC_RPATH */ + uint32_t cmdsize; /* includes string */ + union lc_str path; /* path to add to run path */ +}; + +/* + * The linkedit_data_command contains the offsets and sizes of a blob + * of data in the __LINKEDIT segment. + */ +struct linkedit_data_command { + uint32_t cmd; /* LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, + LC_FUNCTION_STARTS, LC_DATA_IN_CODE, + LC_DYLIB_CODE_SIGN_DRS, + LC_LINKER_OPTIMIZATION_HINT, + LC_DYLD_EXPORTS_TRIE, or + LC_DYLD_CHAINED_FIXUPS. */ + uint32_t cmdsize; /* sizeof(struct linkedit_data_command) */ + uint32_t dataoff; /* file offset of data in __LINKEDIT segment */ + uint32_t datasize; /* file size of data in __LINKEDIT segment */ +}; + +struct fileset_entry_command { + uint32_t cmd; /* LC_FILESET_ENTRY */ + uint32_t cmdsize; /* includes id string */ + uint64_t vmaddr; /* memory address of the dylib */ + uint64_t fileoff; /* file offset of the dylib */ + union lc_str entry_id; /* contained entry id */ + uint32_t reserved; /* entry_id is 32-bits long, so this is the reserved padding */ +}; + +/* + * The encryption_info_command contains the file offset and size of an + * of an encrypted segment. + */ +struct encryption_info_command { + uint32_t cmd; /* LC_ENCRYPTION_INFO */ + uint32_t cmdsize; /* sizeof(struct encryption_info_command) */ + uint32_t cryptoff; /* file offset of encrypted range */ + uint32_t cryptsize; /* file size of encrypted range */ + uint32_t cryptid; /* which enryption system, + 0 means not-encrypted yet */ +}; + +/* + * The encryption_info_command_64 contains the file offset and size of an + * of an encrypted segment (for use in x86_64 targets). + */ +struct encryption_info_command_64 { + uint32_t cmd; /* LC_ENCRYPTION_INFO_64 */ + uint32_t cmdsize; /* sizeof(struct encryption_info_command_64) */ + uint32_t cryptoff; /* file offset of encrypted range */ + uint32_t cryptsize; /* file size of encrypted range */ + uint32_t cryptid; /* which enryption system, + 0 means not-encrypted yet */ + uint32_t pad; /* padding to make this struct's size a multiple + of 8 bytes */ +}; + +/* + * The version_min_command contains the min OS version on which this + * binary was built to run. + */ +struct version_min_command { + uint32_t cmd; /* LC_VERSION_MIN_MACOSX or + LC_VERSION_MIN_IPHONEOS or + LC_VERSION_MIN_WATCHOS or + LC_VERSION_MIN_TVOS */ + uint32_t cmdsize; /* sizeof(struct min_version_command) */ + uint32_t version; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ +}; + +/* + * The build_version_command contains the min OS version on which this + * binary was built to run for its platform. The list of known platforms and + * tool values following it. + */ +struct build_version_command { + uint32_t cmd; /* LC_BUILD_VERSION */ + uint32_t cmdsize; /* sizeof(struct build_version_command) plus */ + /* ntools * sizeof(struct build_tool_version) */ + uint32_t platform; /* platform */ + uint32_t minos; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t ntools; /* number of tool entries following this */ +}; + +struct build_tool_version { + uint32_t tool; /* enum for the tool */ + uint32_t version; /* version number of the tool */ +}; + +/* Known values for the platform field above. */ +#define PLATFORM_MACOS 1 +#define PLATFORM_IOS 2 +#define PLATFORM_TVOS 3 +#define PLATFORM_WATCHOS 4 +#define PLATFORM_BRIDGEOS 5 +#define PLATFORM_MACCATALYST 6 +#define PLATFORM_IOSSIMULATOR 7 +#define PLATFORM_TVOSSIMULATOR 8 +#define PLATFORM_WATCHOSSIMULATOR 9 +#define PLATFORM_DRIVERKIT 10 +#define PLATFORM_MAX PLATFORM_DRIVERKIT +/* Addition of simulated platfrom also needs to update proc_is_simulated() */ + +/* Known values for the tool field above. */ +#define TOOL_CLANG 1 +#define TOOL_SWIFT 2 +#define TOOL_LD 3 + +/* + * The dyld_info_command contains the file offsets and sizes of + * the new compressed form of the information dyld needs to + * load the image. This information is used by dyld on Mac OS X + * 10.6 and later. All information pointed to by this command + * is encoded using byte streams, so no endian swapping is needed + * to interpret it. + */ +struct dyld_info_command { + uint32_t cmd; /* LC_DYLD_INFO or LC_DYLD_INFO_ONLY */ + uint32_t cmdsize; /* sizeof(struct dyld_info_command) */ + + /* + * Dyld rebases an image whenever dyld loads it at an address different + * from its preferred address. The rebase information is a stream + * of byte sized opcodes whose symbolic names start with REBASE_OPCODE_. + * Conceptually the rebase information is a table of tuples: + * + * The opcodes are a compressed way to encode the table by only + * encoding when a column changes. In addition simple patterns + * like "every n'th offset for m times" can be encoded in a few + * bytes. + */ + uint32_t rebase_off; /* file offset to rebase info */ + uint32_t rebase_size; /* size of rebase info */ + + /* + * Dyld binds an image during the loading process, if the image + * requires any pointers to be initialized to symbols in other images. + * The bind information is a stream of byte sized + * opcodes whose symbolic names start with BIND_OPCODE_. + * Conceptually the bind information is a table of tuples: + * + * The opcodes are a compressed way to encode the table by only + * encoding when a column changes. In addition simple patterns + * like for runs of pointers initialzed to the same value can be + * encoded in a few bytes. + */ + uint32_t bind_off; /* file offset to binding info */ + uint32_t bind_size; /* size of binding info */ + + /* + * Some C++ programs require dyld to unique symbols so that all + * images in the process use the same copy of some code/data. + * This step is done after binding. The content of the weak_bind + * info is an opcode stream like the bind_info. But it is sorted + * alphabetically by symbol name. This enable dyld to walk + * all images with weak binding information in order and look + * for collisions. If there are no collisions, dyld does + * no updating. That means that some fixups are also encoded + * in the bind_info. For instance, all calls to "operator new" + * are first bound to libstdc++.dylib using the information + * in bind_info. Then if some image overrides operator new + * that is detected when the weak_bind information is processed + * and the call to operator new is then rebound. + */ + uint32_t weak_bind_off; /* file offset to weak binding info */ + uint32_t weak_bind_size; /* size of weak binding info */ + + /* + * Some uses of external symbols do not need to be bound immediately. + * Instead they can be lazily bound on first use. The lazy_bind + * are contains a stream of BIND opcodes to bind all lazy symbols. + * Normal use is that dyld ignores the lazy_bind section when + * loading an image. Instead the static linker arranged for the + * lazy pointer to initially point to a helper function which + * pushes the offset into the lazy_bind area for the symbol + * needing to be bound, then jumps to dyld which simply adds + * the offset to lazy_bind_off to get the information on what + * to bind. + */ + uint32_t lazy_bind_off; /* file offset to lazy binding info */ + uint32_t lazy_bind_size; /* size of lazy binding infs */ + + /* + * The symbols exported by a dylib are encoded in a trie. This + * is a compact representation that factors out common prefixes. + * It also reduces LINKEDIT pages in RAM because it encodes all + * information (name, address, flags) in one small, contiguous range. + * The export area is a stream of nodes. The first node sequentially + * is the start node for the trie. + * + * Nodes for a symbol start with a uleb128 that is the length of + * the exported symbol information for the string so far. + * If there is no exported symbol, the node starts with a zero byte. + * If there is exported info, it follows the length. + * + * First is a uleb128 containing flags. Normally, it is followed by + * a uleb128 encoded offset which is location of the content named + * by the symbol from the mach_header for the image. If the flags + * is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags is + * a uleb128 encoded library ordinal, then a zero terminated + * UTF8 string. If the string is zero length, then the symbol + * is re-export from the specified dylib with the same name. + * If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following + * the flags is two uleb128s: the stub offset and the resolver offset. + * The stub is used by non-lazy pointers. The resolver is used + * by lazy pointers and must be called to get the actual address to use. + * + * After the optional exported symbol information is a byte of + * how many edges (0-255) that this node has leaving it, + * followed by each edge. + * Each edge is a zero terminated UTF8 of the addition chars + * in the symbol, followed by a uleb128 offset for the node that + * edge points to. + * + */ + uint32_t export_off; /* file offset to lazy binding info */ + uint32_t export_size; /* size of lazy binding infs */ +}; + +/* + * The following are used to encode rebasing information + */ +#define REBASE_TYPE_POINTER 1 +#define REBASE_TYPE_TEXT_ABSOLUTE32 2 +#define REBASE_TYPE_TEXT_PCREL32 3 + +#define REBASE_OPCODE_MASK 0xF0 +#define REBASE_IMMEDIATE_MASK 0x0F +#define REBASE_OPCODE_DONE 0x00 +#define REBASE_OPCODE_SET_TYPE_IMM 0x10 +#define REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB 0x20 +#define REBASE_OPCODE_ADD_ADDR_ULEB 0x30 +#define REBASE_OPCODE_ADD_ADDR_IMM_SCALED 0x40 +#define REBASE_OPCODE_DO_REBASE_IMM_TIMES 0x50 +#define REBASE_OPCODE_DO_REBASE_ULEB_TIMES 0x60 +#define REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB 0x70 +#define REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB 0x80 + + +/* + * The following are used to encode binding information + */ +#define BIND_TYPE_POINTER 1 +#define BIND_TYPE_TEXT_ABSOLUTE32 2 +#define BIND_TYPE_TEXT_PCREL32 3 + +#define BIND_SPECIAL_DYLIB_SELF 0 +#define BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE -1 +#define BIND_SPECIAL_DYLIB_FLAT_LOOKUP -2 +#define BIND_SPECIAL_DYLIB_WEAK_LOOKUP -3 + +#define BIND_SYMBOL_FLAGS_WEAK_IMPORT 0x1 +#define BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION 0x8 + +#define BIND_OPCODE_MASK 0xF0 +#define BIND_IMMEDIATE_MASK 0x0F +#define BIND_OPCODE_DONE 0x00 +#define BIND_OPCODE_SET_DYLIB_ORDINAL_IMM 0x10 +#define BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB 0x20 +#define BIND_OPCODE_SET_DYLIB_SPECIAL_IMM 0x30 +#define BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM 0x40 +#define BIND_OPCODE_SET_TYPE_IMM 0x50 +#define BIND_OPCODE_SET_ADDEND_SLEB 0x60 +#define BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB 0x70 +#define BIND_OPCODE_ADD_ADDR_ULEB 0x80 +#define BIND_OPCODE_DO_BIND 0x90 +#define BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB 0xA0 +#define BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED 0xB0 +#define BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB 0xC0 +#define BIND_OPCODE_THREADED 0xD0 +#define BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB 0x00 +#define BIND_SUBOPCODE_THREADED_APPLY 0x01 + + +/* + * The following are used on the flags byte of a terminal node + * in the export information. + */ +#define EXPORT_SYMBOL_FLAGS_KIND_MASK 0x03 +#define EXPORT_SYMBOL_FLAGS_KIND_REGULAR 0x00 +#define EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL 0x01 +#define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02 +#define EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION 0x04 +#define EXPORT_SYMBOL_FLAGS_REEXPORT 0x08 +#define EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER 0x10 + +/* + * The linker_option_command contains linker options embedded in object files. + */ +struct linker_option_command { + uint32_t cmd; /* LC_LINKER_OPTION only used in MH_OBJECT filetypes */ + uint32_t cmdsize; + uint32_t count; /* number of strings */ + /* concatenation of zero terminated UTF8 strings. + Zero filled at end to align */ +}; + +/* + * The symseg_command contains the offset and size of the GNU style + * symbol table information as described in the header file . + * The symbol roots of the symbol segments must also be aligned properly + * in the file. So the requirement of keeping the offsets aligned to a + * multiple of a 4 bytes translates to the length field of the symbol + * roots also being a multiple of a long. Also the padding must again be + * zeroed. (THIS IS OBSOLETE and no longer supported). + */ +struct symseg_command { + uint32_t cmd; /* LC_SYMSEG */ + uint32_t cmdsize; /* sizeof(struct symseg_command) */ + uint32_t offset; /* symbol segment offset */ + uint32_t size; /* symbol segment size in bytes */ +}; + +/* + * The ident_command contains a free format string table following the + * ident_command structure. The strings are null terminated and the size of + * the command is padded out with zero bytes to a multiple of 4 bytes/ + * (THIS IS OBSOLETE and no longer supported). + */ +struct ident_command { + uint32_t cmd; /* LC_IDENT */ + uint32_t cmdsize; /* strings that follow this command */ +}; + +/* + * The fvmfile_command contains a reference to a file to be loaded at the + * specified virtual address. (Presently, this command is reserved for + * internal use. The kernel ignores this command when loading a program into + * memory). + */ +struct fvmfile_command { + uint32_t cmd; /* LC_FVMFILE */ + uint32_t cmdsize; /* includes pathname string */ + union lc_str name; /* files pathname */ + uint32_t header_addr; /* files virtual address */ +}; + + +/* + * The entry_point_command is a replacement for thread_command. + * It is used for main executables to specify the location (file offset) + * of main(). If -stack_size was used at link time, the stacksize + * field will contain the stack size need for the main thread. + */ +struct entry_point_command { + uint32_t cmd; /* LC_MAIN only used in MH_EXECUTE filetypes */ + uint32_t cmdsize; /* 24 */ + uint64_t entryoff; /* file (__TEXT) offset of main() */ + uint64_t stacksize;/* if not zero, initial stack size */ +}; + + +/* + * The source_version_command is an optional load command containing + * the version of the sources used to build the binary. + */ +struct source_version_command { + uint32_t cmd; /* LC_SOURCE_VERSION */ + uint32_t cmdsize; /* 16 */ + uint64_t version; /* A.B.C.D.E packed as a24.b10.c10.d10.e10 */ +}; + + +/* + * The LC_DATA_IN_CODE load commands uses a linkedit_data_command + * to point to an array of data_in_code_entry entries. Each entry + * describes a range of data in a code section. + */ +struct data_in_code_entry { + uint32_t offset; /* from mach_header to start of data range*/ + uint16_t length; /* number of bytes in data range */ + uint16_t kind; /* a DICE_KIND_* value */ +}; +#define DICE_KIND_DATA 0x0001 +#define DICE_KIND_JUMP_TABLE8 0x0002 +#define DICE_KIND_JUMP_TABLE16 0x0003 +#define DICE_KIND_JUMP_TABLE32 0x0004 +#define DICE_KIND_ABS_JUMP_TABLE32 0x0005 + + + +/* + * Sections of type S_THREAD_LOCAL_VARIABLES contain an array + * of tlv_descriptor structures. + */ +struct tlv_descriptor +{ + void* (*thunk)(struct tlv_descriptor*); + unsigned long key; + unsigned long offset; +}; + +/* + * LC_NOTE commands describe a region of arbitrary data included in a Mach-O + * file. Its initial use is to record extra data in MH_CORE files. + */ +struct note_command { + uint32_t cmd; /* LC_NOTE */ + uint32_t cmdsize; /* sizeof(struct note_command) */ + char data_owner[16]; /* owner name for this LC_NOTE */ + uint64_t offset; /* file offset of this data */ + uint64_t size; /* length of data region */ +}; + +#endif /* _MACHO_LOADER_H_ */ diff --git a/III. Checksec/python/CrimsonUroboros.py b/III. Checksec/python/CrimsonUroboros.py new file mode 100755 index 0000000..53cd481 --- /dev/null +++ b/III. Checksec/python/CrimsonUroboros.py @@ -0,0 +1,720 @@ +#!/usr/bin/env python3 +import lief +import uuid +import argparse +import subprocess +import os +import sys +import mmap +import plistlib +import json + +'''*** REMAINDER *** +Change initialization in MachOProcessoer -> process -> try block. +Always initialize the latest Snake class: + +snake_instance = SnakeII(binaries) +''' +### --- I. MACH-O --- ### +class MachOProcessor: + def __init__(self, file_path): + '''This class contains part of the code from the main() for the SnakeI: Mach-O part.''' + self.file_path = file_path + + def parseFatBinary(self): + return lief.MachO.parse(self.file_path) + + def process(self): + '''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. + print(f'The file {self.file_path} does not exist.') + exit() + + try: # Check if the file has a valid Mach-O format + global binaries # It must be global, becuase after the class is destructed, the snake_instance would point to invalid memory ("binary" is dependant on "binaries"). + binaries = self.parseFatBinary() + if binaries == None: + exit() # Exit if not + + global snake_instance # Must be globall for further processors classes. + snake_instance = SnakeIII(binaries) # Initialize the latest Snake class + + 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.encryption_info is not None: # Print encryption info and save encrypted data if path is specified + if snake_instance.binary.has_encryption_info: + crypt_id, crypt_offset, crypt_size = snake_instance.getEncryptionInfo() + print(f"cryptid: {crypt_id}") + print(f"cryptoffset: {hex(crypt_offset)}") + print(f"cryptsize: {hex(crypt_size)}") + save_path = args.encryption_info + if save_path and save_path.strip(): + snake_instance.saveEcryptedData(save_path.strip()) + else: + print(f"{os.path.basename(file_path)} binary does not have encryption info.") + 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) + if snake_instance.binary.has_encryption_info: + print('\n<=== ENCRYPTION INFO ===>') + crypt_id, crypt_offset, crypt_size = snake_instance.getEncryptionInfo() + print(f"cryptid: {crypt_id}") + print(f"cryptoffset: {hex(crypt_offset)}") + print(f"cryptsize: {hex(crypt_size)}") + 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)}') + except Exception as e: # Handling any unexpected errors + print(f"An error occurred during Mach-O processing: {e}") + exit() +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 + + def getEncryptionInfo(self): + '''Return information regardles to LC_ENCRYPTION_INFO(_64).''' + if self.binary.has_encryption_info: + crypt_id = self.binary.encryption_info.crypt_id + crypt_offset = self.binary.encryption_info.crypt_offset + crypt_size = self.binary.encryption_info.crypt_size + return crypt_id, crypt_offset, crypt_size + + def extractBytesAtOffset(self, offset, size): + '''Extract bytes at a given offset and of a specified size in a binary file (takes into account Fat Binary slide)''' + # Open the binary file in binary mode + with open(file_path, "rb") as file: + # Check if the specified offset and size are within bounds + file_size = os.path.getsize(file_path) + offset += self.fat_offset # Add the fat_offset in case of the Fat Binary (ARM binary data is most of the time after x86_64 binary data) + #print(hex(offset) + hex(size)) + if offset + size > file_size: + raise ValueError("Offset and size exceed the binary file's length.") + # Seek to the offset considering the fat_offset + file.seek(offset) + # Read the specified size of bytes + extracted_bytes = file.read(size) + return extracted_bytes + + def saveEcryptedData(self,output_path): + _, cryptoff, cryptsize = self.getEncryptionInfo() + self.saveBytesToFile(self.extractBytesAtOffset(cryptoff, cryptsize), output_path) +### --- II. CODE SIGNING --- ### +class CodeSigningProcessor: + def __init__(self): + '''This class contains part of the code from the main() for the SnakeII: Code Signing.''' + pass + + def process(self): + try: + if args.verify_signature: # Verify if Code Signature match the binary content () + if snake_instance.isSigValid(file_path): + print("Valid Code Signature (matches the content)") + else: + print("Invalid Code Signature (does not match the content)") + if args.cd_info: # Print Code Signature information + print(snake_instance.getCodeSignature(file_path).decode('utf-8')) + if args.cd_requirements: # Print Requirements. + print(snake_instance.getCodeSignatureRequirements(file_path).decode('utf-8')) + if args.entitlements: # Print Entitlements. + print(snake_instance.getEntitlementsFromCodeSignature(file_path,args.entitlements)) + if args.extract_cms: # Extract the CMS Signature and save it to a given file. + cms_signature = snake_instance.extractCMS() + snake_instance.saveBytesToFile(cms_signature, args.extract_cms) + if args.extract_certificates: # Extract Certificates and save them to a given file. + snake_instance.extractCertificatesFromCodeSignature(args.extract_certificates) + if args.remove_sig: # Save a new file on a disk with the removed signature: + snake_instance.removeCodeSignature(args.remove_sig) + if args.sign_binary: # Sign the given binary using specified identity: + snake_instance.signBinary(args.sign_binary) + except Exception as e: + print(f"An error occurred during Code Signing processing: {e}") +class SnakeII(SnakeI): + def __init__(self, binaries): + super().__init__(binaries) + self.magic_bytes = (0xFADE0B01).to_bytes(4, byteorder='big') # CMS Signature Blob magic bytes, as Code Signature as a whole is in network byte order(big endian). + + def isSigValid(self, file_path): + '''Checks if the Code Signature is valid (if the contents of the binary have been modified.)''' + result = subprocess.run(["codesign", "-v", file_path], capture_output=True) + if result.stderr == b'': + return True + else: + return False + + def getCodeSignature(self, file_path): + '''Returns information about the Code Signature.''' + result = subprocess.run(["codesign", "-d", "-vvvvvv", file_path], capture_output=True) + return result.stderr + + def getCodeSignatureRequirements(self, file_path): + '''Returns information about the Code Signature Requirements.''' + result = subprocess.run(["codesign", "-d", "-r", "-", file_path], capture_output=True) + return result.stdout + + def getEntitlementsFromCodeSignature(self, file_path, format=None): + '''Returns information about the Entitlements for Code Signature.''' + if format == 'human' or format == None: + result = subprocess.run(["codesign", "-d", "--entitlements", "-", file_path], capture_output=True) + return result.stdout.decode('utf-8') + elif format == 'xml': + result = subprocess.run(["codesign", "-d", "--entitlements", "-", "--xml", file_path], capture_output=True) + elif format == 'der': + result = subprocess.run(["codesign", "-d", "--entitlements", "-", "--der", file_path], capture_output=True) + return result.stdout + + def extractCMS(self): + '''Find the offset of magic bytes in a binary using LIEF.''' + cs = self.binary.code_signature + cs_content = bytes(cs.content) + offset = cs_content.find(self.magic_bytes) + cms_len_in_bytes = cs_content[offset + 4:offset + 8] + cms_len_in_int = int.from_bytes(cms_len_in_bytes, byteorder='big') + cms_signature = cs_content[offset + 8:offset + 8 + cms_len_in_int] + return cms_signature + + def saveBytesToFile(self, data, filename): + '''Save bytes to a file.''' + with open(filename, 'wb') as file: + file.write(data) + + def extractCertificatesFromCodeSignature(self, cert_name): + '''Extracts certificates from the CMS Signature and saves them to a file with _0, _1, _2 indexes at the end of the file names.''' + subprocess.run(["codesign", "-d", f"--extract-certificates={cert_name}_", file_path], capture_output=True) + + def removeCodeSignature(self, new_name): + '''Save new file on a disk with removed signature.''' + self.binary.remove_signature() + self.binary.write(new_name) + + def signBinary(self,security_identity=None): + '''Sign binary using pseudo identity (adhoc) or specified identity.''' + if security_identity == 'adhoc' or security_identity == None: + result = subprocess.run(["codesign", "-s", "-", "-f", file_path], capture_output=True) + return result.stdout.decode('utf-8') + else: + try: + result = subprocess.run(["codesign", "-s", security_identity, "-f", file_path], capture_output=True) + except Exception as e: + print(f"An error occurred during Code Signing using {security_identity}\n {e}") +### --- III. CHECKSEC --- ### +class ChecksecProcessor: + def __init__(self): + '''This class contains part of the code from the main() for the SnakeIII: Checksec.''' + pass + + def process(self): + try: + if args.has_pie: # Check if PIE is set in the header flags + print("PIE: " + str(snake_instance.hasPIE())) + if args.has_arc: # Check if ARC is in use + print("ARC: " + str(snake_instance.hasARC())) + if args.is_stripped: # Check if binary is stripped + print("STRIPPED: " + str(snake_instance.isStripped())) + if args.has_canary: # Check if binary has stack canary + print("CANARY: " + str(snake_instance.hasCanary())) + if args.has_nx_stack: # Check if binary has non executable stack + print("NX STACK: " + str(snake_instance.hasNXstack())) + if args.has_nx_heap: # Check if binary has non executable heap + print("NX HEAP: " + str(snake_instance.hasNXheap())) + if args.has_xn: # Check if binary is protected by eXecute Never functionality + print(f"eXecute Never: {str(snake_instance.hasXN())}") + if args.is_notarized: # Check if the application is notarized and can pass the Gatekeeper verification + print("NOTARIZED: " + str(snake_instance.isNotarized(file_path))) + if args.is_encrypted: # Check if the application has encrypted data + print("ENCRYPTED: " + str(snake_instance.isEncrypted())) + if args.has_restrict: # Check if the application has encrypted data + print("RESTRICTED: " + str(snake_instance.hasRestrictSegment())) + if args.is_hr: # Check if Hardened Runtime is in use + print("HARDENED: " + str(snake_instance.hasHardenedRuntimeFlag(file_path))) + if args.is_as: # Check if App Sandbox is in use + print("APP SANDBOX: " + str(snake_instance.hasAppSandbox())) + if args.is_fort: # Check if binary is fortified + fortified_symbols = snake_instance.getForifiedSymbols() + print("FORTIFIED: " + str(snake_instance.isFortified(fortified_symbols))) + if args.has_rpath: # Check if binary has @rpaths + print("RPATH: " + str(snake_instance.hasRpath())) + if args.checksec: # Run all checks from above and present it in a table + print("<==== CHECKSEC ======") + print("PIE: ".ljust(16) + str(snake_instance.hasPIE())) + print("ARC: ".ljust(16) + str(snake_instance.hasARC())) + print("STRIPPED: ".ljust(16) + str(snake_instance.isStripped())) + print("CANARY: ".ljust(16) + str(snake_instance.hasCanary())) + print("NX STACK: ".ljust(16) + str(snake_instance.hasNXstack())) + print("NX HEAP: ".ljust(16) + str(snake_instance.hasNXheap())) + print("XN:".ljust(16) + str(snake_instance.hasXN())) + print("NOTARIZED: ".ljust(16) + str(snake_instance.isNotarized(file_path))) + print("ENCRYPTED: ".ljust(16) + str(snake_instance.isEncrypted())) + print("RESTRICTED: ".ljust(16) + str(snake_instance.hasRestrictSegment())) + print("HARDENED: ".ljust(16) + str(snake_instance.hasHardenedRuntimeFlag(file_path))) + print("APP SANDBOX: ".ljust(16) + str(snake_instance.hasAppSandbox())) + fortified_symbols = snake_instance.getForifiedSymbols() + print("FORTIFIED: ".ljust(16) + str(snake_instance.isFortified(fortified_symbols))) + print("RPATH: ".ljust(16) + str(snake_instance.hasRpath())) + print("=====================>") + except Exception as e: + print(f"An error occurred during Checksec processing: {e}") +class SnakeIII(SnakeII): + def __init__(self, binaries): + super().__init__(binaries) + + def hasPIE(self): + '''Check if MH_PIE (0x00200000) is set in the header flags.''' + return self.binary.is_pie + + def hasARC(self): + '''Check if the _objc_release symbol is imported.''' + for symbol in self.binary.symbols: + if symbol.name.lower().strip() == '_objc_release': + return True + return False + + def isStripped(self): + '''Check if binary is stripped.''' + filter_symbols = ['radr://5614542', '__mh_execute_header'] + + for symbol in self.binary.symbols: + symbol_type = symbol.type + symbol_name = symbol.name.lower().strip() + + is_symbol_stripped = (symbol_type & 0xe0 > 0) or (symbol_type in [0x0e, 0x1e, 0x0f]) + is_filtered = symbol_name not in filter_symbols + + if is_symbol_stripped and is_filtered: + return False + return True + + def hasCanary(self): + '''Check whether in the binary there are symbols: ___stack_chk_fail and ___stack_chk_guard.''' + canary_symbols = ['___stack_chk_fail', '___stack_chk_guard'] + for symbol in self.binary.symbols: + if symbol.name.lower().strip() in canary_symbols: + return True + return False + + def hasNXstack(self): + '''Check if MH_ALLOW_STACK_EXECUTION (0x00020000 ) is not set in the header flags.''' + return not bool(self.binary.header.flags & lief.MachO.HEADER_FLAGS.ALLOW_STACK_EXECUTION.value) + + def hasNXheap(self): + '''Check if MH_NO_HEAP_EXECUTION (0x01000000 ) is set in the header flags.''' + return bool(self.binary.header.flags & lief.MachO.HEADER_FLAGS.NO_HEAP_EXECUTION.value) + + def isXNos(): + '''Check if the OS is running on the ARM architecture.''' + system_info = os.uname() + if "arm" in system_info.machine.lower(): + return True + return False + + def checkXNmap(): + '''If XN is ON, you will not be able to map memory page that has W&X at the same time, so to check it, you can create such page.''' + try: + mmap.mmap(-1,4096, prot=mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC) + except mmap.error as e: + #print(f"Failed to create W&X memory map - eXecute Never is supported on this machine. \n {str(e)}") + return True + return False + + def convertXMLEntitlementsToDict(self, entitlements_xml): + '''Takes the Entitlements in XML format from getEntitlementsFromCodeSignature() method and convert them to a dictionary.''' + return plistlib.loads(entitlements_xml) + + def convertDictEntitlementsToJson(self,entitlements_dict): + '''Takes the Entitlements in dictionary format from convertXMLEntitlementsToDict() method and convert them to a JSON with indent 4.''' + return json.dumps(entitlements_dict, indent=4) + + def checkIfEntitlementIsUsed(self, entitlement_name, entitlement_value): + '''Check if the given entitlement exists and has the specified value.''' + try: + entitlements_xml = self.getEntitlementsFromCodeSignature(file_path, 'xml') + if entitlements_xml == b'': # Return False if there are no entitlements + return False + entitlements_dict = self.convertXMLEntitlementsToDict(entitlements_xml) + # Convert the entire parsed data to lowercase for case-insensitive comparison + parsed_data = {key.lower(): value for key, value in entitlements_dict.items()} + # Convert entitlement name and value to lowercase for case-insensitive and type-insensitive comparison + entitlement_name_lower = entitlement_name.lower() + entitlement_value_lower = str(entitlement_value).lower() + + if entitlement_name_lower in parsed_data and str(parsed_data[entitlement_name_lower]).lower() == entitlement_value_lower: + return True + else: + return False + except json.JSONDecodeError as e: + # Handle JSON decoding error if any + print(f"Error in checkIfEntitlementIsUsed: {e}") + return False + + def hasAllowJITentitlement(self): + '''Checks if the binary has missing com.apple.security.cs.allow-jit entitlement that allows the app to create writable and executable memory using the MAP_JIT flag.''' + if self.checkIfEntitlementIsUsed('com.apple.security.cs.allow-jit', 'true'): + print(f"[INFO -> XN]: {os.path.basename(file_path)} contains allow-jit entitlement.") + return True + return False + + def checkIfCompiledForOtherThanARM(self): + '''Iterates over FatBinary and check if there are other architectures than ARM.''' + XN_types = [lief.MachO.CPU_TYPES.ARM64, lief.MachO.CPU_TYPES.ARM] + for binary in binaries: + if binary.header.cpu_type not in XN_types: + print(f"[INFO -> XN]: {os.path.basename(file_path)} is compiled for other CPUs than ARM or ARM64.") + return True + return False + + def hasXN(self): + '''Check if binary allows W&X via com.apple.security.cs.allow-jit entitlement or is compiled for other CPU types than these which supports eXecuteNever feature of ARM.''' + if self.hasAllowJITentitlement() or self.checkIfCompiledForOtherThanARM(): + return False + return True + + def isNotarized(self, file_path): + '''Verifies if the application is notarized and can pass the Gatekeeper verification.''' + result = subprocess.run(["spctl", "-a", file_path], capture_output=True) + if result.stderr == b'': + return True + else: + #print(f"[INFO -> NOTARIZATION]: {result.stderr.decode().rstrip()}") + return False + + def isEncrypted(self): + '''If the cryptid has a non-zero value, some parts of the binary are encrypted.''' + if self.binary.has_encryption_info: + if self.binary.encryption_info.crypt_id == 1: + return True + return False + + def hasRestrictSegment(self): + '''Check if binary contains __RESTRICT segment. Return True if it does.''' + for segment in self.binary.segments: + if segment.name.lower().strip() == "__restrict": + return True + return False + + def hasHardenedRuntimeFlag(self, file_path): + '''Check if Hardened Runtime flag is set for the given binary.''' + if b'runtime' in self.getCodeSignature(file_path): + return True + return False + + def hasAppSandbox(self): + '''Check if App Sandbox is in use (com.apple.security.app-sandbox entitlement is set).''' + if self.checkIfEntitlementIsUsed('com.apple.security.app-sandbox', 'true'): + return True + return False + + def getForifiedSymbols(self): + '''Check for symbol names that contain _chk suffix and filter out stack canary symbols. Function returns a list of all safe symbols.''' + symbol_fiter = ['___stack_chk_fail', '___stack_chk_guard'] + fortified_symbols = [] + for symbol in self.binary.symbols: + symbol_name = symbol.name.lower().strip() + if ('_chk' in symbol_name) and (symbol_name not in symbol_fiter): + fortified_symbols.append(symbol_name) + return fortified_symbols + + def isFortified(self, fortified_symbols): + '''Check if there are any fortified symbols in the give fortified_symbols list.''' + if len(fortified_symbols) > 0: + return True + return False + + def hasRpath(self): + return self.binary.has_rpath +### --- ARGUMENT PARSER --- ### +class ArgumentParser: + def __init__(self): + '''Class for parsing arguments from the command line. I decided to remove it from main() for additional readability and easier code maintenance in the VScode''' + self.parser = argparse.ArgumentParser(description="Mach-O files parser for binary analysis") + self.addGeneralArgs() + self.addMachOArgs() + self.addCodeSignArgs() + self.addChecksecArgs() + + def addGeneralArgs(self): + self.parser.add_argument('-p', '--path', required=True, help="Path to the Mach-O file") + + def addMachOArgs(self): + macho_group = self.parser.add_argument_group('MACH-O ARGS') + macho_group.add_argument('--file_type', action='store_true', help="Print binary file type") + macho_group.add_argument('--header_flags', action='store_true', help="Print binary header flags") + macho_group.add_argument('--endian', action='store_true', help="Print binary endianess") + macho_group.add_argument('--header', action='store_true', help="Print binary header") + macho_group.add_argument('--load_commands', action='store_true', help="Print binary load commands names") + macho_group.add_argument('--segments', action='store_true', help="Print binary segments in human-friendly form") + macho_group.add_argument('--sections', action='store_true', help="Print binary sections in human-friendly form") + macho_group.add_argument('--symbols', action='store_true', help="Print all binary symbols") + macho_group.add_argument('--chained_fixups', action='store_true', help="Print Chained Fixups information") + macho_group.add_argument('--exports_trie', action='store_true', help="Print Export Trie information") + macho_group.add_argument('--uuid', action='store_true', help="Print UUID") + macho_group.add_argument('--main', action='store_true', help="Print entry point and stack size") + macho_group.add_argument('--encryption_info', nargs='?',const='', help="Print encryption info if any. Optionally specify an output path to dump the encrypted data (if cryptid=0, data will be in plain text)", metavar="(optional) save_path.bytes") + macho_group.add_argument('--strings_section', action='store_true', help="Print strings from __cstring section") + macho_group.add_argument('--all_strings', action='store_true', help="Print strings from all sections") + macho_group.add_argument('--save_strings', help="Parse all sections, detect strings, and save them to a file", metavar='all_strings.txt') + macho_group.add_argument('--info', action='store_true', default=False, help="Print header, load commands, segments, sections, symbols, and strings") + + def addCodeSignArgs(self): + codesign_group = self.parser.add_argument_group('CODE SIGNING ARGS') + codesign_group.add_argument('--verify_signature', action='store_true', default=False, help="Code Signature verification (if the contents of the binary have been modified)") + codesign_group.add_argument('--cd_info', action='store_true', default=False, help="Print Code Signature information") + codesign_group.add_argument('--cd_requirements', action='store_true', default=False, help="Print Code Signature Requirements") + codesign_group.add_argument('--entitlements', help="Print Entitlements in a human-readable, XML, or DER format (default: human)", nargs='?', const='human', metavar='human|xml|var') + codesign_group.add_argument('--extract_cms', help="Extract CMS Signature from the Code Signature and save it to a given file", metavar='cms_signature.der') + codesign_group.add_argument('--extract_certificates', help="Extract Certificates and save them to a given file. To each filename will be added an index at the end: _0 for signing, _1 for intermediate, and _2 for root CA certificate", metavar='certificate_name') + codesign_group.add_argument('--remove_sig', help="Save the new file on a disk with removed signature", metavar='unsigned_binary') + 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_number') + + def addChecksecArgs(self): + checksec_group = self.parser.add_argument_group('CHECKSEC ARGS') + checksec_group.add_argument('--has_pie', action='store_true', default=False, help="Check if Position-Independent Executable (PIE) is set") + checksec_group.add_argument('--has_arc', action='store_true', default=False, help="Check if Automatic Reference Counting (ARC) is in use (can be false positive)") + checksec_group.add_argument('--is_stripped', action='store_true', default=False, help="Check if binary is stripped") + checksec_group.add_argument('--has_canary', action='store_true', default=False, help="Check if Stack Canary is in use (can be false positive)") + checksec_group.add_argument('--has_nx_stack', action='store_true', default=False, help="Check if stack is non-executable (NX stack)") + checksec_group.add_argument('--has_nx_heap', action='store_true', default=False, help="Check if heap is non-executable (NX heap)") + checksec_group.add_argument('--has_xn', action='store_true', default=False, help="Check if binary is protected by eXecute Never (XN) ARM protection") + checksec_group.add_argument('--is_notarized', action='store_true', default=False, help="Check if the application is notarized and can pass the Gatekeeper verification") + checksec_group.add_argument('--is_encrypted', action='store_true', default=False, help="Check if the application is encrypted (has LC_ENCRYPTION_INFO(_64) and cryptid set to 1)") + checksec_group.add_argument('--has_restrict', action='store_true', default=False, help="Check if binary has __RESTRICT segment") + checksec_group.add_argument('--is_hr', action='store_true', default=False, help="Check if the Hardened Runtime is in use") + checksec_group.add_argument('--is_as', action='store_true', default=False, help="Check if the App Sandbox is in use") + checksec_group.add_argument('--is_fort', action='store_true', default=False, help="Check if the binary is fortified") + checksec_group.add_argument('--has_rpath', action='store_true', default=False, help="Check if the binary utilise any @rpath variables") + checksec_group.add_argument('--checksec', action='store_true', default=False, help="Run all checksec module options on the binary") + + def parseArgs(self): + return self.parser.parse_args() + + def printAllArgs(self, args): + '''Just for debugging. This method is a utility designed to print all parsed arguments and their corresponding values.''' + for arg, value in vars(args).items(): + print(f"{arg}: {value}") + +if __name__ == "__main__": + arg_parser = ArgumentParser() + args = arg_parser.parseArgs() + + file_path = os.path.abspath(args.path) + + ### --- I. MACH-O --- ### + macho_processor = MachOProcessor(file_path) + macho_processor.process() + + ### --- II. CODE SIGNING --- ### + code_signing_processor = CodeSigningProcessor() + code_signing_processor.process() + + ### --- III. CHECKSEC --- ### + checksec_processor = ChecksecProcessor() + checksec_processor.process() \ No newline at end of file diff --git a/III. Checksec/python/LCFinder.py b/III. Checksec/python/LCFinder.py new file mode 100755 index 0000000..e406690 --- /dev/null +++ b/III. Checksec/python/LCFinder.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +import argparse +import lief + + +class LCFinder: + def __init__(self, args): + """ + Initialize CheckLC object. + + :param args: Command-line arguments. + """ + self.path = args.path if args.path else None + self.list_path = args.list_path if args.list_path else None + self.load_command = args.lc + + def parseFatBinary(self, binaries): + """ + Parse the fat binary and return the ARM64 binary if found. + + :param binaries: List of binaries in the fat binary. + :return: ARM64 binary if found, else None. + """ + arm64_bin = None + for binary in binaries: + if binary.header.cpu_type == lief.MachO.CPU_TYPES.ARM64: + arm64_bin = binary + return arm64_bin + + def getLoadCommands(self, binary): + """ + Get the list of load commands from the binary. + + :param binary: MachO binary. + :return: List of load commands. + """ + return binary.commands + + def checkLoadCommand(self, binary, target_load_command): + """ + Check if the specified load command is present in the binary. + + :param binary: MachO binary. + :param target_load_command: The load command to check for. + :return: True if the load command is found, else False. + """ + load_commands_list = self.getLoadCommands(binary) + for lc in load_commands_list: + load_command = str(lc.command) + parts = load_command.split('.') + name = parts[-1] + lc_name = "LC_" + name + lc_filter = [name.lower(), lc_name.lower()] + if target_load_command.lower() in lc_filter: + return True + return False + + def processPath(self, path): + """ + Process a single binary file. + + :param path: Path to the binary file. + """ + try: + binary = lief.MachO.parse(path) + arm64_bin = self.parseFatBinary(binary) + if arm64_bin and self.checkLoadCommand(arm64_bin, self.load_command): + print(f"Load Command '{self.load_command}' found in: {path}") + except Exception as e: + print(f"Error processing {path}: {e}") + + def processList(self, list_path): + """ + Process a list of binary files. + + :param list_path: Path to the file containing a list of binary paths. + """ + try: + with open(list_path, 'r') as file: + paths = file.readlines() + for path in paths: + self.processPath(path.strip()) + except Exception as e: + print(f"Error processing list: {e}") + + def run(self): + """ + Run the check based on provided input. + """ + if self.path: + self.processPath(self.path) + elif self.list_path: + self.processList(self.list_path) + else: + print("Please provide either a single path or a list path.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Check for a specific load command in Mach-O binaries.") + parser.add_argument("--path", "-p", help="Absolute path to the valid MachO binary.") + parser.add_argument("--list_path", "-l", help="Path to a wordlist file containing absolute paths.") + parser.add_argument("--lc", help="The load command to check for.", required=True) + + args = parser.parse_args() + checker = LCFinder(args) + checker.run() diff --git a/III. Checksec/python/ModifyMachOFlags.py b/III. Checksec/python/ModifyMachOFlags.py new file mode 100755 index 0000000..6589c06 --- /dev/null +++ b/III. Checksec/python/ModifyMachOFlags.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +import lief +import argparse +import subprocess + +class ModifyMachOFlags: + """Class for modifying Mach-O binary flags and signing the binary.""" + + def __init__(self, input_path=None, output_path=None, sign_identity=None): + """Initialize the ModifyMachOFlags instance with input, output, and signing identity.""" + self.input_path = input_path + self.output_path = output_path + self.sign_identity = sign_identity + self.macho_flags = { + 'NOUNDEFS': 0x1, + 'INCRLINK': 0x2, + 'DYLDLINK': 0x4, + 'BINDATLOAD': 0x8, + 'PREBOUND': 0x10, + 'SPLIT_SEGS': 0x20, + 'LAZY_INIT': 0x40, + 'TWOLEVEL': 0x80, + 'FORCE_FLAT': 0x100, + 'NOMULTIDEFS': 0x200, + 'NOFIXPREBINDING': 0x400, + 'PREBINDABLE': 0x800, + 'ALLMODSBOUND': 0x1000, + 'SUBSECTIONS_VIA_SYMBOLS': 0x2000, + 'CANONICAL': 0x4000, + 'WEAK_DEFINES': 0x8000, + 'BINDS_TO_WEAK': 0x10000, + 'ALLOW_STACK_EXECUTION': 0x20000, + 'ROOT_SAFE': 0x40000, + 'SETUID_SAFE': 0x80000, + 'NO_REEXPORTED_DYLIBS': 0x100000, + 'PIE': 0x200000, + 'DEAD_STRIPPABLE_DYLIB': 0x400000, + 'HAS_TLV_DESCRIPTORS': 0x800000, + 'NO_HEAP_EXECUTION': 0x1000000, + 'APP_EXTENSION_SAFE': 0x02000000, + 'NLIST_OUTOFSYNC_WITH_DYLDINFO': 0x04000000, + 'SIM_SUPPORT': 0x08000000, + 'DYLIB_IN_CACHE': 0x80000000, + } + + def parseFatBinary(self, binaries, arch): + """Parse the specified architecture from the given binaries.""" + bin_by_arch = next((bin for bin in binaries if bin.header.cpu_type == arch), None) + if bin_by_arch is None: + print(f'The specified Mach-O file is not in {arch} architecture.') + exit() + return bin_by_arch + + def modifyMachOFlags(self, flags): + """Modify Mach-O binary flags based on the provided dictionary of flags and values.""" + try: + binaries = lief.MachO.parse(self.input_path) + except Exception as e: + print(f"An error occurred: {e}") + exit() + + arch = lief.MachO.CPU_TYPES.ARM64 # Modify the architecture as needed + binary = self.parseFatBinary(binaries, arch) + + for flag, value in flags.items(): + self.setFlag(binary, flag, value) + + binary.write(self.output_path) + + def signBinary(self): + """Sign the modified binary using the specified or default identity.""" + if self.sign_identity: + if self.sign_identity == 'adhoc': + subprocess.run(["codesign", "-s", "-", "-f", self.output_path], check=True) + else: + subprocess.run(["codesign", "-s", self.sign_identity, "-f", self.output_path], check=True) + + def setFlag(self, binary, flag, value): + """Set or clear the specified flag in the Mach-O binary based on the given value.""" + if value: + binary.header.flags |= flag + else: + binary.header.flags &= ~flag + +if __name__ == "__main__": + default_instance = ModifyMachOFlags() # Create an instance with default values + + parser = argparse.ArgumentParser(description="Modify the Mach-O binary flags.") + parser.add_argument('-i', '--input', required=True, help="Path to the Mach-O file.") + parser.add_argument('-o', '--out', required=True, help="Where to save a modified file.") + parser.add_argument('--flag', action='append', type=str, help=f"Specify the flag constant name and value (e.g., NO_HEAP_EXECUTION=1). Can be used multiple times. Available flags: \n{', '.join(default_instance.macho_flags.keys())}\n") + parser.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_number') + + args = parser.parse_args() + + modifier = ModifyMachOFlags(args.input, args.out, args.sign_binary) + + # Process flags provided by the user + flags = {} + if args.flag: + for flag_str in args.flag: + flag_parts = flag_str.split('=') + if len(flag_parts) == 2: + flag_name, flag_value = flag_parts + flag_name = flag_name.upper() + if flag_name in modifier.macho_flags: + flags[modifier.macho_flags[flag_name]] = int(flag_value) + else: + print(f"Invalid flag constant: {flag_name}") + exit() + else: + print(f"Invalid flag format: {flag_str}") + exit() + + modifier.modifyMachOFlags(flags) + modifier.signBinary() \ No newline at end of file diff --git a/README.md b/README.md index 7526292..dc24228 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Each article directory contains three subdirectories: * ☐ [IV. Dylibs]() ## TOOLS -### [CrimsonUroboros](II.%20Code%20Signing//python/CrimsonUroboros.py) +### [CrimsonUroboros](III.%20Checksec/python/CrimsonUroboros.py) ![alt](img/CrimsonUroboros.jpg) Core program resulting from the Snake&Apple article series for binary analysis. You may find older versions of this script in each article directory in this repository. * Usage @@ -163,6 +163,55 @@ Designed to extract cms sginature from Mach-O files (bash alternative to `Singat ./extract_cms.sh target_binary cms_sign ``` *** +### [ModifyMachOFlags](III.%20Checksec/python/ModifyMachOFlags.py) +Designed to change Mach-O header flags. +* Usage: +```console +usage: ModifyMachOFlags [-h] -i INPUT -o OUT [--flag FLAG] [--sign_binary [adhoc|identity_number]] + +Modify the Mach-O binary flags. + +options: + -h, --help show this help message and exit + -i INPUT, --input INPUT + Path to the Mach-O file. + -o OUT, --out OUT Where to save a modified file. + --flag FLAG Specify the flag constant name and value (e.g., NO_HEAP_EXECUTION=1). Can be used multiple times. Available + flags: NOUNDEFS, INCRLINK, DYLDLINK, BINDATLOAD, PREBOUND, SPLIT_SEGS, LAZY_INIT, TWOLEVEL, FORCE_FLAT, + NOMULTIDEFS, NOFIXPREBINDING, PREBINDABLE, ALLMODSBOUND, SUBSECTIONS_VIA_SYMBOLS, CANONICAL, WEAK_DEFINES, + BINDS_TO_WEAK, ALLOW_STACK_EXECUTION, ROOT_SAFE, SETUID_SAFE, NO_REEXPORTED_DYLIBS, PIE, + DEAD_STRIPPABLE_DYLIB, HAS_TLV_DESCRIPTORS, NO_HEAP_EXECUTION, APP_EXTENSION_SAFE, + NLIST_OUTOFSYNC_WITH_DYLDINFO, SIM_SUPPORT, DYLIB_IN_CACHE + --sign_binary [adhoc|identity_number] + Sign binary using specified identity - use : 'security find-identity -v -p codesigning' to get the + identity. (default: adhoc) +``` +* Example: +```bash +ModifyMachOFlags -i hello -o hello_modified --flag NO_HEAP_EXECUTION=1 --sign_binary +``` +*** +### [LCFinder](III.%20Checksec/python/LCFinder.py) +Designed to find if specified Load Command exist in the binary or list of binaries. +* Usage: +```console +usage: LCFinder [-h] [--path PATH] [--list_path LIST_PATH] --lc LC + +Check for a specific load command in Mach-O binaries. + +options: + -h, --help show this help message and exit + --path PATH, -p PATH Absolute path to the valid MachO binary. + --list_path LIST_PATH, -l LIST_PATH + Path to a wordlist file containing absolute paths. + --lc LC The load command to check for. +``` +* Example: +```bash +LCFinder -l macho_paths.txt --lc SEGMENT_64 2>/dev/null +LCFinder -p hello --lc lc_segment_64 2>/dev/null +``` +*** ## INSTALL ``` pip -r requirements.txt @@ -181,4 +230,11 @@ I will write the code for each article as a class SnakeX, where X will be the ar ## ADDITIONAL LINKS * [Apple Open Source](https://opensource.apple.com/releases/) * [XNU](https://github.com/apple-oss-distributions/xnu) -* [dyld](https://github.com/apple-oss-distributions/dyld) \ No newline at end of file +* [dyld](https://github.com/apple-oss-distributions/dyld) + +## TODO +* DER Entitlements converter method - currently, only the `convert_xml_entitlements_to_dict()` method exists. I need to create a Python parser for DER-encoded entitlements. +* SuperBlob parser - to find other blobs in Code Signature. +* Entitlements Blob parser - to check if XML and DER blobs exist. +* Every method in the Snake class that use Entitlements should parse first XML > DER (currently, only XML parser exists) +* After making a SuperBlob parser and CodeDirectory blob parser, modify hasHardenedRuntime to check Runtime flag by using bitmask, instead of string. \ No newline at end of file