diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..398205e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +**/.DS_Store +**/.vscode \ No newline at end of file diff --git a/II. Code Signing/custom/extract_cms.sh b/II. Code Signing/custom/extract_cms.sh new file mode 100755 index 0000000..8888931 --- /dev/null +++ b/II. Code Signing/custom/extract_cms.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +# Path to the binary +binary_path="$1" +cms_sign_out="$2" + +# Extract magic bytes offset +binary_in_hex=$(xxd -p -u -c0 "$binary_path") +offset=$(echo -n "$binary_in_hex" | grep -ob 'FADE0B01' | awk -F: 'NR==1{print $1}') + +# CMS data starts after the magic bytes and length, so you must add 8B to the offset value. +CMS_offset_in_dec=$(( ($offset / 2) + 8)) + +# Extract blob length +CMS_length=$(echo -n "$binary_in_hex" | awk 'match($0, /FADE0B01/) { print substr($0, RSTART + RLENGTH, 8) }') + +# Extract the CMS Signature from the binary +dd bs=1 skip="$CMS_offset_in_dec" count="0x$CMS_length" if="$binary_path" of="$cms_sign_out" 2>/dev/null diff --git a/II. Code Signing/mac/CSCommon.h b/II. Code Signing/mac/CSCommon.h new file mode 100644 index 0000000..af547b7 --- /dev/null +++ b/II. Code Signing/mac/CSCommon.h @@ -0,0 +1,399 @@ +// Extracted from Xcode 15 Beta 7 +// /Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk/System/Library/Frameworks/Security.framework/Versions/A/Headers/CSCommon.h */ +/* + * Copyright (c) 2006-2014 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@ + */ +/*! + @header CSCommon + CSCommon is the common header of all Code Signing API headers. + It defines types, constants, and error codes. +*/ +#ifndef _H_CSCOMMON +#define _H_CSCOMMON + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* + * Some macOS API's use the old style defined name CSSM_DATA and CSSM_OID. + * These are just typedefs for SecAsn* which are available for iOS. We complete + * those here in case they're not available for compatibility. + */ +#if TARGET_OS_IPHONE + +#ifndef CSSM_DATA +#define CSSM_DATA SecAsn1Item +#endif + +#ifndef CSSM_OID +#define CSSM_OID SecAsn1Oid +#endif + +#endif /* TARGET_OS_IPHONE */ + +CF_ASSUME_NONNULL_BEGIN + +/* + Code Signing specific OSStatus codes. + [Assigned range 0xFFFE_FAxx]. +*/ +CF_ENUM(OSStatus) { + errSecCSUnimplemented = -67072, /* unimplemented code signing feature */ + errSecCSInvalidObjectRef = -67071, /* invalid API object reference */ + errSecCSInvalidFlags = -67070, /* invalid or inappropriate API flag(s) specified */ + errSecCSObjectRequired = -67069, /* a required pointer argument was NULL */ + errSecCSStaticCodeNotFound = -67068, /* cannot find code object on disk */ + errSecCSUnsupportedGuestAttributes = -67067, /* cannot locate guests using this attribute set */ + errSecCSInvalidAttributeValues = -67066, /* given attribute values are invalid */ + errSecCSNoSuchCode = -67065, /* host has no guest with the requested attributes */ + errSecCSMultipleGuests = -67064, /* ambiguous guest specification (host has multiple guests with these attribute values) */ + errSecCSGuestInvalid = -67063, /* code identity has been invalidated */ + errSecCSUnsigned = -67062, /* code object is not signed at all */ + errSecCSSignatureFailed = -67061, /* invalid signature (code or signature have been modified) */ + errSecCSSignatureNotVerifiable = -67060, /* the code cannot be read by the verifier (file system permissions etc.) */ + errSecCSSignatureUnsupported = -67059, /* unsupported type or version of signature */ + errSecCSBadDictionaryFormat = -67058, /* a required plist file or resource is malformed */ + errSecCSResourcesNotSealed = -67057, /* resources are present but not sealed by signature */ + errSecCSResourcesNotFound = -67056, /* code has no resources but signature indicates they must be present */ + errSecCSResourcesInvalid = -67055, /* the sealed resource directory is invalid */ + errSecCSBadResource = -67054, /* a sealed resource is missing or invalid */ + errSecCSResourceRulesInvalid = -67053, /* invalid resource specification rule(s) */ + errSecCSReqInvalid = -67052, /* invalid or corrupted code requirement(s) */ + errSecCSReqUnsupported = -67051, /* unsupported type or version of code requirement(s) */ + errSecCSReqFailed = -67050, /* code failed to satisfy specified code requirement(s) */ + errSecCSBadObjectFormat = -67049, /* object file format unrecognized, invalid, or unsuitable */ + errSecCSInternalError = -67048, /* internal error in Code Signing subsystem */ + errSecCSHostReject = -67047, /* code rejected its host */ + errSecCSNotAHost = -67046, /* attempt to specify guest of code that is not a host */ + errSecCSSignatureInvalid = -67045, /* invalid or unsupported format for signature */ + errSecCSHostProtocolRelativePath = -67044, /* host protocol violation - absolute guest path required */ + errSecCSHostProtocolContradiction = -67043, /* host protocol violation - contradictory hosting modes */ + errSecCSHostProtocolDedicationError = -67042, /* host protocol violation - operation not allowed with/for a dedicated guest */ + errSecCSHostProtocolNotProxy = -67041, /* host protocol violation - proxy hosting not engaged */ + errSecCSHostProtocolStateError = -67040, /* host protocol violation - invalid guest state change request */ + errSecCSHostProtocolUnrelated = -67039, /* host protocol violation - the given guest is not a guest of the given host */ + /* -67038 obsolete (no longer issued) */ + errSecCSNotSupported = -67037, /* operation inapplicable or not supported for this type of code */ + errSecCSCMSTooLarge = -67036, /* signature too large to embed (size limitation of on-disk representation) */ + errSecCSHostProtocolInvalidHash = -67035, /* host protocol violation - invalid guest hash */ + errSecCSStaticCodeChanged = -67034, /* the code on disk does not match what is running */ + errSecCSDBDenied = -67033, /* permission to use a database denied */ + errSecCSDBAccess = -67032, /* cannot access a database */ + errSecCSSigDBDenied = -67033, /* permission to use a database denied */ + errSecCSSigDBAccess = -67032, /* cannot access a database */ + errSecCSHostProtocolInvalidAttribute = -67031, /* host returned invalid or inconsistent guest attributes */ + errSecCSInfoPlistFailed = -67030, /* invalid Info.plist (plist or signature have been modified) */ + errSecCSNoMainExecutable = -67029, /* the code has no main executable file */ + errSecCSBadBundleFormat = -67028, /* bundle format unrecognized, invalid, or unsuitable */ + errSecCSNoMatches = -67027, /* no matches for search or update operation */ + errSecCSFileHardQuarantined = -67026, /* File created by an AppSandbox, exec/open not allowed */ + errSecCSOutdated = -67025, /* presented data is out of date */ + errSecCSDbCorrupt = -67024, /* a system database or file is corrupt */ + errSecCSResourceDirectoryFailed = -67023, /* invalid resource directory (directory or signature have been modified) */ + errSecCSUnsignedNestedCode = -67022, /* nested code is unsigned */ + errSecCSBadNestedCode = -67021, /* nested code is modified or invalid */ + errSecCSBadCallbackValue = -67020, /* monitor callback returned invalid value */ + errSecCSHelperFailed = -67019, /* the codesign_allocate helper tool cannot be found or used */ + errSecCSVetoed = -67018, + errSecCSBadLVArch = -67017, /* library validation flag cannot be used with an i386 binary */ + errSecCSResourceNotSupported = -67016, /* unsupported resource found (something not a directory, file or symlink) */ + errSecCSRegularFile = -67015, /* the main executable or Info.plist must be a regular file (no symlinks, etc.) */ + errSecCSUnsealedAppRoot = -67014, /* unsealed contents present in the bundle root */ + errSecCSWeakResourceRules = -67013, /* resource envelope is obsolete (custom omit rules) */ + errSecCSDSStoreSymlink = -67012, /* .DS_Store files cannot be a symlink */ + errSecCSAmbiguousBundleFormat = -67011, /* bundle format is ambiguous (could be app or framework) */ + errSecCSBadMainExecutable = -67010, /* main executable failed strict validation */ + errSecCSBadFrameworkVersion = -67009, /* embedded framework contains modified or invalid version */ + errSecCSUnsealedFrameworkRoot = -67008, /* unsealed contents present in the root directory of an embedded framework */ + errSecCSWeakResourceEnvelope = -67007, /* resource envelope is obsolete (version 1 signature) */ + errSecCSCancelled = -67006, /* operation was terminated by explicit cancelation */ + errSecCSInvalidPlatform = -67005, /* invalid platform identifier or platform mismatch */ + errSecCSTooBig = -67004, /* code is too big for current signing format */ + errSecCSInvalidSymlink = -67003, /* invalid destination for symbolic link in bundle */ + errSecCSNotAppLike = -67002, /* the code is valid but does not seem to be an app */ + errSecCSBadDiskImageFormat = -67001, /* disk image format unrecognized, invalid, or unsuitable */ + errSecCSUnsupportedDigestAlgorithm = -67000, /* a requested signature digest algorithm is not supported */ + errSecCSInvalidAssociatedFileData = -66999, /* resource fork, Finder information, or similar detritus not allowed */ + errSecCSInvalidTeamIdentifier = -66998, /* a Team Identifier string is invalid */ + errSecCSBadTeamIdentifier = -66997, /* a Team Identifier is wrong or inappropriate */ + errSecCSSignatureUntrusted = -66996, /* signature is valid but signer is not trusted */ + errSecMultipleExecSegments = -66995, /* the image contains multiple executable segments */ + errSecCSInvalidEntitlements = -66994, /* invalid entitlement plist */ + errSecCSInvalidRuntimeVersion = -66993, /* an invalid runtime version was explicitly set */ + errSecCSRevokedNotarization = -66992, /* notarization indicates this code has been revoked */ + errSecCSCMSConstructionFailed = -66991, /* CMS construction failed, see logs for deeper error */ + errSecCSRemoteSignerFailed = -66990, /* remote signing block did not return a signature */ +}; + +/* + * Code Signing specific CFError "user info" keys. + * In calls that can return CFErrorRef indications, if a CFErrorRef is actually + * returned, its "user info" dictionary may contain some of the following keys + * to more closely describe the circumstances of the failure. + * Do not rely on the presence of any particular key to categorize a problem; + * always use the primary OSStatus return for that. The data contained under + * these keys is always supplemental and optional. + */ +extern const CFStringRef kSecCFErrorArchitecture; /* CFStringRef: name of architecture causing the problem */ +extern const CFStringRef kSecCFErrorPattern; /* CFStringRef: invalid resource selection pattern encountered */ +extern const CFStringRef kSecCFErrorResourceSeal; /* CFTypeRef: invalid component in resource seal (CodeResources) */ +extern const CFStringRef kSecCFErrorResourceAdded; /* CFURLRef: unsealed resource found */ +extern const CFStringRef kSecCFErrorResourceAltered; /* CFURLRef: modified resource found */ +extern const CFStringRef kSecCFErrorResourceMissing; /* CFURLRef: sealed (non-optional) resource missing */ +extern const CFStringRef kSecCFErrorResourceSideband; /* CFURLRef: sealed resource has invalid sideband data (resource fork, etc.) */ +extern const CFStringRef kSecCFErrorResourceRecursive; /* CFURLRef: resource is main executable, resulting in infinite recursion */ +extern const CFStringRef kSecCFErrorInfoPlist; /* CFTypeRef: Info.plist dictionary or component thereof found invalid */ +extern const CFStringRef kSecCFErrorGuestAttributes; /* CFTypeRef: Guest attribute set of element not accepted */ +extern const CFStringRef kSecCFErrorRequirementSyntax; /* CFStringRef: compilation error for Requirement source */ +extern const CFStringRef kSecCFErrorPath; /* CFURLRef: subcomponent containing the error */ + +/*! + @typedef SecCodeRef + This is the type of a reference to running code. + + In many (but not all) calls, this can be passed to a SecStaticCodeRef + argument, which performs an implicit SecCodeCopyStaticCode call and + operates on the result. +*/ +typedef struct CF_BRIDGED_TYPE(id) __SecCode *SecCodeRef; /* running code */ + +/*! + @typedef SecStaticCodeRef + This is the type of a reference to static code on disk. +*/ +typedef struct CF_BRIDGED_TYPE(id) __SecCode const *SecStaticCodeRef; /* code on disk */ + +/*! + @typedef SecRequirementRef + This is the type of a reference to a code requirement. +*/ +typedef struct CF_BRIDGED_TYPE(id) __SecRequirement *SecRequirementRef; /* code requirement */ + + +/*! + @typedef SecGuestRef + An abstract handle to identify a particular Guest in the context of its Host. + + Guest handles are assigned by the host at will, with kSecNoGuest (zero) being + reserved as the null value. They can be reused for new children if desired. +*/ +typedef u_int32_t SecGuestRef; + +CF_ENUM(SecGuestRef) { + kSecNoGuest = 0, /* not a valid SecGuestRef */ +}; + + +/*! + @typedef SecCSFlags + This is the type of flags arguments to Code Signing API calls. + It provides a bit mask of request and option flags. All of the bits in these + masks are reserved to Apple; if you set any bits not defined in these headers, + the behavior is generally undefined. + + This list describes the flags that are shared among several Code Signing API calls. + Flags that only apply to one call are defined and documented with that call. + Global flags are assigned from high order down (31 -> 0); call-specific flags + are assigned from the bottom up (0 -> 31). + + @constant kSecCSDefaultFlags + When passed to a flags argument throughout, indicates that default behavior + is desired. Do not mix with other flags values. + @constant kSecCSConsiderExpiration + When passed to a call that performs code validation, requests that code signatures + made by expired certificates be rejected. By default, expiration of participating + certificates is not automatic grounds for rejection. + @constant kSecCSNoNetworkAccess + When passed to a call that performs code validation, configures the validation to + not perform any work that requires the network. Using this flag disables security features + like online certificate revocation and notarization checks by removing potentially + slow network requests that can delay evaluations. This flag has always been usable for + SecStaticCode objects and is usable with SecCode objects starting with macOS 11.3. +*/ +typedef CF_OPTIONS(uint32_t, SecCSFlags) { + kSecCSDefaultFlags = 0, /* no particular flags (default behavior) */ + + kSecCSConsiderExpiration = 1U << 31, /* consider expired certificates invalid */ + kSecCSEnforceRevocationChecks = 1 << 30, /* force revocation checks regardless of preference settings */ + kSecCSNoNetworkAccess = 1 << 29, /* do not use the network, cancels "kSecCSEnforceRevocationChecks" */ + kSecCSReportProgress = 1 << 28, /* make progress report call-backs when configured */ + kSecCSCheckTrustedAnchors = 1 << 27, /* build certificate chain to system trust anchors, not to any self-signed certificate */ + kSecCSQuickCheck = 1 << 26, /* (internal) */ + kSecCSApplyEmbeddedPolicy = 1 << 25, /* Apply Embedded (iPhone) policy regardless of the platform we're running on */ + kSecCSStripDisallowedXattrs = 1 << 24, /* Strip disallowed xattrs, such as com.apple.FinderInfo and com.apple.ResourceFork */ +}; + + +/*! + @typedef SecCodeSignatureFlags + This is the type of option flags that can be embedded in a code signature + during signing, and that govern the use of the signature thereafter. + Some of these flags can be set through the codesign(1) command's --options + argument; some are set implicitly based on signing circumstances; and all + can be set with the kSecCodeSignerFlags item of a signing information dictionary. + + @constant kSecCodeSignatureHost + Indicates that the code may act as a host that controls and supervises guest + code. If this flag is not set in a code signature, the code is never considered + eligible to be a host, and any attempt to act like one will be ignored or rejected. + @constant kSecCodeSignatureAdhoc + The code has been sealed without a signing identity. No identity may be retrieved + from it, and any code requirement placing restrictions on the signing identity + will fail. This flag is set by the code signing API and cannot be set explicitly. + @constant kSecCodeSignatureForceHard + Implicitly set the "hard" status bit for the code when it starts running. + This bit indicates that the code prefers to be denied access to a resource + if gaining such access would cause its invalidation. Since the hard bit is + sticky, setting this option bit guarantees that the code will always have + it set. + @constant kSecCodeSignatureForceKill + Implicitly set the "kill" status bit for the code when it starts running. + This bit indicates that the code wishes to be terminated with prejudice if + it is ever invalidated. Since the kill bit is sticky, setting this option bit + guarantees that the code will always be dynamically valid, since it will die + immediately if it becomes invalid. + @constant kSecCodeSignatureForceExpiration + Forces the kSecCSConsiderExpiration flag on all validations of the code. + @constant kSecCodeSignatureRuntime + Instructs the kernel to apply runtime hardening policies as required by the + hardened runtime version + @constant kSecCodeSignatureLinkerSigned + The code was automatically signed by the linker. This signature should be + ignored in any new signing operation. + */ +typedef CF_OPTIONS(uint32_t, SecCodeSignatureFlags) { + kSecCodeSignatureHost = 0x0001, /* may host guest code */ + kSecCodeSignatureAdhoc = 0x0002, /* must be used without signer */ + kSecCodeSignatureForceHard = 0x0100, /* always set HARD mode on launch */ + kSecCodeSignatureForceKill = 0x0200, /* always set KILL mode on launch */ + kSecCodeSignatureForceExpiration = 0x0400, /* force certificate expiration checks */ + kSecCodeSignatureRestrict = 0x0800, /* restrict dyld loading */ + kSecCodeSignatureEnforcement = 0x1000, /* enforce code signing */ + kSecCodeSignatureLibraryValidation = 0x2000, /* library validation required */ + kSecCodeSignatureRuntime = 0x10000, /* apply runtime hardening policies */ + kSecCodeSignatureLinkerSigned = 0x20000, /* identify that the signature was auto-generated by the linker*/ +}; + +/*! + @typedef SecCodeStatus + The code signing system attaches a set of status flags to each running code. + These flags are maintained by the code's host, and can be read by anyone. + A code may change its own flags, a host may change its guests' flags, + and root may change anyone's flags. However, these flags are sticky in that + each can change in only one direction (and never back, for the lifetime of the code). + Not even root can violate this restriction. + + There are other flags in SecCodeStatus that are not publicly documented. + Do not rely on them, and do not ever attempt to explicitly set them. + + @constant kSecCodeStatusValid + Indicates that the code is dynamically valid, i.e. it started correctly + and has not been invalidated since then. The valid bit can only be cleared. + + Warning: This bit is not your one-stop shortcut to determining the validity of code. + It represents the dynamic component of the full validity function; if this + bit is unset, the code is definitely invalid, but the converse is not always true. + In fact, code hosts may represent the outcome of some delayed static validation work in this bit, + and thus it strictly represents a blend of (all of) dynamic and (some of) static validity, + depending on the implementation of the particular host managing the code. You can (only) + rely that (1) dynamic invalidation will clear this bit; and (2) the combination + of static validation and dynamic validity (as performed by the SecCodeCheckValidity* APIs) + will give a correct answer. + + @constant kSecCodeStatusHard + Indicates that the code prefers to be denied access to resources if gaining access + would invalidate it. This bit can only be set. + It is undefined whether code that is marked hard and is already invalid will still + be denied access to a resource that would invalidate it if it were still valid. That is, + the code may or may not get access to such a resource while being invalid, and that choice + may appear random. + + @constant kSecCodeStatusKill + Indicates that the code wants to be killed (terminated) if it ever loses its validity. + This bit can only be set. Code that has the kill flag set will never be dynamically invalid + (and live). Note however that a change in static validity does not necessarily trigger instant + death. + + @constant kSecCodeStatusDebugged + Indicated that code has been debugged by another process that was allowed to do so. The debugger + causes this to be set when it attachs. + + @constant kSecCodeStatusPlatform + Indicates the code is platform code, shipping with the operating system and signed by Apple. +*/ +typedef CF_OPTIONS(uint32_t, SecCodeStatus) { + kSecCodeStatusValid = 0x00000001, + kSecCodeStatusHard = 0x00000100, + kSecCodeStatusKill = 0x00000200, + kSecCodeStatusDebugged = 0x10000000, + kSecCodeStatusPlatform = 0x04000000, +}; + + +/*! + @typedef SecRequirementType + An enumeration indicating different types of internal requirements for code. + */ +typedef CF_ENUM(uint32_t, SecRequirementType) { + kSecHostRequirementType = 1, /* what hosts may run us */ + kSecGuestRequirementType = 2, /* what guests we may run */ + kSecDesignatedRequirementType = 3, /* designated requirement */ + kSecLibraryRequirementType = 4, /* what libraries we may link against */ + kSecPluginRequirementType = 5, /* what plug-ins we may load */ + kSecInvalidRequirementType, /* invalid type of Requirement (must be last) */ + kSecRequirementTypeCount = kSecInvalidRequirementType /* number of valid requirement types */ +}; + + +/*! + Types of cryptographic digests (hashes) used to hold code signatures + together. + + Each combination of type, length, and other parameters is a separate + hash type; we don't understand "families" here. + + These type codes govern the digest links that connect a CodeDirectory + to its subordinate data structures (code pages, resources, etc.) + They do not directly control other uses of hashes (such as those used + within X.509 certificates and CMS blobs). + */ +typedef CF_ENUM(uint32_t, SecCSDigestAlgorithm) { + kSecCodeSignatureNoHash = 0, /* null value */ + kSecCodeSignatureHashSHA1 = 1, /* SHA-1 */ + kSecCodeSignatureHashSHA256 = 2, /* SHA-256 */ + kSecCodeSignatureHashSHA256Truncated = 3, /* SHA-256 truncated to first 20 bytes */ + kSecCodeSignatureHashSHA384 = 4, /* SHA-384 */ + kSecCodeSignatureHashSHA512 = 5, /* SHA-512 */ +}; + +CF_ASSUME_NONNULL_END + +#ifdef __cplusplus +} +#endif + +#endif //_H_CSCOMMON \ No newline at end of file diff --git a/II. Code Signing/mac/cs_blobs.h b/II. Code Signing/mac/cs_blobs.h new file mode 100644 index 0000000..c2fb166 --- /dev/null +++ b/II. Code Signing/mac/cs_blobs.h @@ -0,0 +1,317 @@ +// Extracted from Xcode 15 Beta 7 +// /Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/kern/cs_blobs.h +/* + * Copyright (c) 2017 Apple Computer, Inc. All rights reserved. + * @APPLE_OSREFERENCE_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. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * 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_OSREFERENCE_LICENSE_HEADER_END@ + */ +#ifndef _KERN_CODESIGN_H_ +#define _KERN_CODESIGN_H_ + +#include +#include + +/* code signing attributes of a process */ +#define CS_VALID 0x00000001 /* dynamically valid */ +#define CS_ADHOC 0x00000002 /* ad hoc signed */ +#define CS_GET_TASK_ALLOW 0x00000004 /* has get-task-allow entitlement */ +#define CS_INSTALLER 0x00000008 /* has installer entitlement */ + +#define CS_FORCED_LV 0x00000010 /* Library Validation required by Hardened System Policy */ +#define CS_INVALID_ALLOWED 0x00000020 /* (macOS Only) Page invalidation allowed by task port policy */ + +#define CS_HARD 0x00000100 /* don't load invalid pages */ +#define CS_KILL 0x00000200 /* kill process if it becomes invalid */ +#define CS_CHECK_EXPIRATION 0x00000400 /* force expiration checking */ +#define CS_RESTRICT 0x00000800 /* tell dyld to treat restricted */ + +#define CS_ENFORCEMENT 0x00001000 /* require enforcement */ +#define CS_REQUIRE_LV 0x00002000 /* require library validation */ +#define CS_ENTITLEMENTS_VALIDATED 0x00004000 /* code signature permits restricted entitlements */ +#define CS_NVRAM_UNRESTRICTED 0x00008000 /* has com.apple.rootless.restricted-nvram-variables.heritable entitlement */ + +#define CS_RUNTIME 0x00010000 /* Apply hardened runtime policies */ +#define CS_LINKER_SIGNED 0x00020000 /* Automatically signed by the linker */ + +#define CS_ALLOWED_MACHO (CS_ADHOC | CS_HARD | CS_KILL | CS_CHECK_EXPIRATION | \ + CS_RESTRICT | CS_ENFORCEMENT | CS_REQUIRE_LV | CS_RUNTIME | CS_LINKER_SIGNED) + +#define CS_EXEC_SET_HARD 0x00100000 /* set CS_HARD on any exec'ed process */ +#define CS_EXEC_SET_KILL 0x00200000 /* set CS_KILL on any exec'ed process */ +#define CS_EXEC_SET_ENFORCEMENT 0x00400000 /* set CS_ENFORCEMENT on any exec'ed process */ +#define CS_EXEC_INHERIT_SIP 0x00800000 /* set CS_INSTALLER on any exec'ed process */ + +#define CS_KILLED 0x01000000 /* was killed by kernel for invalidity */ +#define CS_NO_UNTRUSTED_HELPERS 0x02000000 /* kernel did not load a non-platform-binary dyld or Rosetta runtime */ +#define CS_DYLD_PLATFORM CS_NO_UNTRUSTED_HELPERS /* old name */ +#define CS_PLATFORM_BINARY 0x04000000 /* this is a platform binary */ +#define CS_PLATFORM_PATH 0x08000000 /* platform binary by the fact of path (osx only) */ + +#define CS_DEBUGGED 0x10000000 /* process is currently or has previously been debugged and allowed to run with invalid pages */ +#define CS_SIGNED 0x20000000 /* process has a signature (may have gone invalid) */ +#define CS_DEV_CODE 0x40000000 /* code is dev signed, cannot be loaded into prod signed code (will go away with rdar://problem/28322552) */ +#define CS_DATAVAULT_CONTROLLER 0x80000000 /* has Data Vault controller entitlement */ + +#define CS_ENTITLEMENT_FLAGS (CS_GET_TASK_ALLOW | CS_INSTALLER | CS_DATAVAULT_CONTROLLER | CS_NVRAM_UNRESTRICTED) + +/* executable segment flags */ + +#define CS_EXECSEG_MAIN_BINARY 0x1 /* executable segment denotes main binary */ +#define CS_EXECSEG_ALLOW_UNSIGNED 0x10 /* allow unsigned pages (for debugging) */ +#define CS_EXECSEG_DEBUGGER 0x20 /* main binary is debugger */ +#define CS_EXECSEG_JIT 0x40 /* JIT enabled */ +#define CS_EXECSEG_SKIP_LV 0x80 /* OBSOLETE: skip library validation */ +#define CS_EXECSEG_CAN_LOAD_CDHASH 0x100 /* can bless cdhash for execution */ +#define CS_EXECSEG_CAN_EXEC_CDHASH 0x200 /* can execute blessed cdhash */ + +/* + * Magic numbers used by Code Signing + */ +enum { + CSMAGIC_REQUIREMENT = 0xfade0c00, /* single Requirement blob */ + CSMAGIC_REQUIREMENTS = 0xfade0c01, /* Requirements vector (internal requirements) */ + CSMAGIC_CODEDIRECTORY = 0xfade0c02, /* CodeDirectory blob */ + CSMAGIC_EMBEDDED_SIGNATURE = 0xfade0cc0, /* embedded form of signature data */ + CSMAGIC_EMBEDDED_SIGNATURE_OLD = 0xfade0b02, /* XXX */ + CSMAGIC_EMBEDDED_ENTITLEMENTS = 0xfade7171, /* embedded entitlements */ + CSMAGIC_EMBEDDED_DER_ENTITLEMENTS = 0xfade7172, /* embedded DER encoded entitlements */ + CSMAGIC_DETACHED_SIGNATURE = 0xfade0cc1, /* multi-arch collection of embedded signatures */ + CSMAGIC_BLOBWRAPPER = 0xfade0b01, /* CMS Signature, among other things */ + CSMAGIC_EMBEDDED_LAUNCH_CONSTRAINT = 0xfade8181, /* Light weight code requirement */ + + CS_SUPPORTSSCATTER = 0x20100, + CS_SUPPORTSTEAMID = 0x20200, + CS_SUPPORTSCODELIMIT64 = 0x20300, + CS_SUPPORTSEXECSEG = 0x20400, + CS_SUPPORTSRUNTIME = 0x20500, + CS_SUPPORTSLINKAGE = 0x20600, + + CSSLOT_CODEDIRECTORY = 0, /* slot index for CodeDirectory */ + CSSLOT_INFOSLOT = 1, + CSSLOT_REQUIREMENTS = 2, + CSSLOT_RESOURCEDIR = 3, + CSSLOT_APPLICATION = 4, + CSSLOT_ENTITLEMENTS = 5, + CSSLOT_DER_ENTITLEMENTS = 7, + CSSLOT_LAUNCH_CONSTRAINT_SELF = 8, + CSSLOT_LAUNCH_CONSTRAINT_PARENT = 9, + CSSLOT_LAUNCH_CONSTRAINT_RESPONSIBLE = 10, + CSSLOT_LIBRARY_CONSTRAINT = 11, + + CSSLOT_ALTERNATE_CODEDIRECTORIES = 0x1000, /* first alternate CodeDirectory, if any */ + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX = 5, /* max number of alternate CD slots */ + CSSLOT_ALTERNATE_CODEDIRECTORY_LIMIT = CSSLOT_ALTERNATE_CODEDIRECTORIES + CSSLOT_ALTERNATE_CODEDIRECTORY_MAX, /* one past the last */ + + CSSLOT_SIGNATURESLOT = 0x10000, /* CMS Signature */ + CSSLOT_IDENTIFICATIONSLOT = 0x10001, + CSSLOT_TICKETSLOT = 0x10002, + + CSTYPE_INDEX_REQUIREMENTS = 0x00000002, /* compat with amfi */ + CSTYPE_INDEX_ENTITLEMENTS = 0x00000005, /* compat with amfi */ + + CS_HASHTYPE_SHA1 = 1, + CS_HASHTYPE_SHA256 = 2, + CS_HASHTYPE_SHA256_TRUNCATED = 3, + CS_HASHTYPE_SHA384 = 4, + + CS_SHA1_LEN = 20, + CS_SHA256_LEN = 32, + CS_SHA256_TRUNCATED_LEN = 20, + + CS_CDHASH_LEN = 20, /* always - larger hashes are truncated */ + CS_HASH_MAX_SIZE = 48, /* max size of the hash we'll support */ + +/* + * Currently only to support Legacy VPN plugins, and Mac App Store + * but intended to replace all the various platform code, dev code etc. bits. + */ + CS_SIGNER_TYPE_UNKNOWN = 0, + CS_SIGNER_TYPE_LEGACYVPN = 5, + CS_SIGNER_TYPE_MAC_APP_STORE = 6, + + CS_SUPPL_SIGNER_TYPE_UNKNOWN = 0, + CS_SUPPL_SIGNER_TYPE_TRUSTCACHE = 7, + CS_SUPPL_SIGNER_TYPE_LOCAL = 8, + + CS_SIGNER_TYPE_OOPJIT = 9, + + /* Validation categories used for trusted launch environment */ + CS_VALIDATION_CATEGORY_INVALID = 0, + CS_VALIDATION_CATEGORY_PLATFORM = 1, + CS_VALIDATION_CATEGORY_TESTFLIGHT = 2, + CS_VALIDATION_CATEGORY_DEVELOPMENT = 3, + CS_VALIDATION_CATEGORY_APP_STORE = 4, + CS_VALIDATION_CATEGORY_ENTERPRISE = 5, + CS_VALIDATION_CATEGORY_DEVELOPER_ID = 6, + CS_VALIDATION_CATEGORY_LOCAL_SIGNING = 7, + CS_VALIDATION_CATEGORY_ROSETTA = 8, + CS_VALIDATION_CATEGORY_OOPJIT = 9, + CS_VALIDATION_CATEGORY_NONE = 10, +}; + +/* The set of application types we support for linkage signatures */ +enum { + CS_LINKAGE_APPLICATION_INVALID = 0, + CS_LINKAGE_APPLICATION_ROSETTA = 1, + + /* XOJIT has been renamed to OOP-JIT */ + CS_LINKAGE_APPLICATION_XOJIT = 2, + CS_LINKAGE_APPLICATION_OOPJIT = 2, +}; + +/* The set of application sub-types we support for linkage signatures */ +enum { + /* + * For backwards compatibility with older signatures, the AOT sub-type is kept + * as 0. + */ + CS_LINKAGE_APPLICATION_ROSETTA_AOT = 0, + + /* OOP-JIT sub-types -- XOJIT type kept for external dependencies */ + CS_LINKAGE_APPLICATION_XOJIT_PREVIEWS = 1, + CS_LINKAGE_APPLICATION_OOPJIT_INVALID = 0, + CS_LINKAGE_APPLICATION_OOPJIT_PREVIEWS = 1, + CS_LINKAGE_APPLICATION_OOPJIT_MLCOMPILER = 2, + CS_LINKAGE_APPLICATION_OOPJIT_TOTAL, +}; + +/* Integer to string conversion of OOP-JIT types */ +static const char *oop_jit_conversion[CS_LINKAGE_APPLICATION_OOPJIT_TOTAL] = { + [CS_LINKAGE_APPLICATION_OOPJIT_INVALID] = NULL, + [CS_LINKAGE_APPLICATION_OOPJIT_PREVIEWS] = "previews", + [CS_LINKAGE_APPLICATION_OOPJIT_MLCOMPILER] = "ml-compiler", +}; + +#define KERNEL_HAVE_CS_CODEDIRECTORY 1 +#define KERNEL_CS_CODEDIRECTORY_HAVE_PLATFORM 1 + +/* + * C form of a CodeDirectory. + */ +typedef struct __CodeDirectory { + uint32_t magic; /* magic number (CSMAGIC_CODEDIRECTORY) */ + uint32_t length; /* total length of CodeDirectory blob */ + uint32_t version; /* compatibility version */ + uint32_t flags; /* setup and mode flags */ + uint32_t hashOffset; /* offset of hash slot element at index zero */ + uint32_t identOffset; /* offset of identifier string */ + uint32_t nSpecialSlots; /* number of special hash slots */ + uint32_t nCodeSlots; /* number of ordinary (code) hash slots */ + uint32_t codeLimit; /* limit to main image signature range */ + uint8_t hashSize; /* size of each hash in bytes */ + uint8_t hashType; /* type of hash (cdHashType* constants) */ + uint8_t platform; /* platform identifier; zero if not platform binary */ + uint8_t pageSize; /* log2(page size in bytes); 0 => infinite */ + uint32_t spare2; /* unused (must be zero) */ + + char end_earliest[0]; + + /* Version 0x20100 */ + uint32_t scatterOffset; /* offset of optional scatter vector */ + char end_withScatter[0]; + + /* Version 0x20200 */ + uint32_t teamOffset; /* offset of optional team identifier */ + char end_withTeam[0]; + + /* Version 0x20300 */ + uint32_t spare3; /* unused (must be zero) */ + uint64_t codeLimit64; /* limit to main image signature range, 64 bits */ + char end_withCodeLimit64[0]; + + /* Version 0x20400 */ + uint64_t execSegBase; /* offset of executable segment */ + uint64_t execSegLimit; /* limit of executable segment */ + uint64_t execSegFlags; /* executable segment flags */ + char end_withExecSeg[0]; + + /* Version 0x20500 */ + uint32_t runtime; + uint32_t preEncryptOffset; + char end_withPreEncryptOffset[0]; + + /* Version 0x20600 */ + uint8_t linkageHashType; + uint8_t linkageApplicationType; + uint16_t linkageApplicationSubType; + uint32_t linkageOffset; + uint32_t linkageSize; + char end_withLinkage[0]; + + /* followed by dynamic content as located by offset fields above */ +} CS_CodeDirectory +__attribute__ ((aligned(1))); + +/* + * Structure of an embedded-signature SuperBlob + */ + +typedef struct __BlobIndex { + uint32_t type; /* type of entry */ + uint32_t offset; /* offset of entry */ +} CS_BlobIndex +__attribute__ ((aligned(1))); + +typedef struct __SC_SuperBlob { + uint32_t magic; /* magic number */ + uint32_t length; /* total length of SuperBlob */ + uint32_t count; /* number of index entries following */ + CS_BlobIndex index[]; /* (count) entries */ + /* followed by Blobs in no particular order as indicated by offsets in index */ +} CS_SuperBlob +__attribute__ ((aligned(1))); + +#define KERNEL_HAVE_CS_GENERICBLOB 1 +typedef struct __SC_GenericBlob { + uint32_t magic; /* magic number */ + uint32_t length; /* total length of blob */ + char data[]; +} CS_GenericBlob +__attribute__ ((aligned(1))); + +typedef struct __SC_Scatter { + uint32_t count; // number of pages; zero for sentinel (only) + uint32_t base; // first page number + uint64_t targetOffset; // offset in target + uint64_t spare; // reserved +} SC_Scatter +__attribute__ ((aligned(1))); + + +/* + * Defined launch types + */ +__enum_decl(cs_launch_type_t, uint8_t, { + CS_LAUNCH_TYPE_NONE = 0, + CS_LAUNCH_TYPE_SYSTEM_SERVICE = 1, + CS_LAUNCH_TYPE_SYSDIAGNOSE = 2, + CS_LAUNCH_TYPE_APPLICATION = 3, +}); + +struct launch_constraint_data { + cs_launch_type_t launch_type; +}; +typedef struct launch_constraint_data* launch_constraint_data_t; + +#endif /* _KERN_CODESIGN_H */ \ No newline at end of file diff --git a/II. Code Signing/mac/trustcache.h b/II. Code Signing/mac/trustcache.h new file mode 100644 index 0000000..655bb78 --- /dev/null +++ b/II. Code Signing/mac/trustcache.h @@ -0,0 +1,85 @@ +// Extracted from Xcode 15 Beta 7 +// /Library/Developer/CommandLineTools/SDKs/MacOSX14.0.sdk/System/Library/Frameworks/Kernel.framework/Versions/A/Headers/kern/trustcache.h */ +/* + * Copyright (c) 2018 Apple Computer, Inc. All rights reserved. + * @APPLE_OSREFERENCE_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. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * 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_OSREFERENCE_LICENSE_HEADER_END@ + */ + +#ifndef _KERN_TRUSTCACHE_H_ +#define _KERN_TRUSTCACHE_H_ + +#include + +#include + +#include + + + +/* Version 1 trust caches: Always sorted by cdhash, added hash type and flags field. + * Suitable for all trust caches. */ + +struct trust_cache_entry1 { + uint8_t cdhash[CS_CDHASH_LEN]; + uint8_t hash_type; + uint8_t flags; +} __attribute__((__packed__)); + +struct trust_cache_module1 { + uint32_t version; + uuid_t uuid; + uint32_t num_entries; + struct trust_cache_entry1 entries[]; +} __attribute__((__packed__)); + +// Trust Cache Entry Flags +#define CS_TRUST_CACHE_AMFID 0x1 // valid cdhash for amfid + +/* Trust Cache lookup functions return their result as a 32bit value + * comprised of subfields, for straightforward passing through layers. + * + * Format: + * + * 0xXXCCBBAA + * + * AA: 0-7: lookup result + * bit 0: TC_LOOKUP_FOUND: set if any entry found + * bit 1: (obsolete) TC_LOOKUP_FALLBACK: set if found in legacy static trust cache + * bit 2-7: reserved + * BB: 8-15: entry flags pass-through, see "Trust Cache Entry Flags" above + * CC: 16-23: code directory hash type of entry, see CS_HASHTYPE_* in cs_blobs.h + * XX: 24-31: reserved + */ + +#define TC_LOOKUP_HASH_TYPE_SHIFT 16 +#define TC_LOOKUP_HASH_TYPE_MASK 0xff0000L; +#define TC_LOOKUP_FLAGS_SHIFT 8 +#define TC_LOOKUP_FLAGS_MASK 0xff00L +#define TC_LOOKUP_RESULT_SHIFT 0 +#define TC_LOOKUP_RESULT_MASK 0xffL + +#define TC_LOOKUP_FOUND 1 + +#endif /* _KERN_TRUSTCACHE_H */ \ No newline at end of file diff --git a/II. Code Signing/python/CrimsonUroboros.py b/II. Code Signing/python/CrimsonUroboros.py new file mode 100755 index 0000000..43ac1f9 --- /dev/null +++ b/II. Code Signing/python/CrimsonUroboros.py @@ -0,0 +1,421 @@ +#!/usr/bin/env python3 +import lief +import uuid +import argparse +import subprocess +import os +import sys + +'''*** 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 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 = lief.MachO.parse(file_path) + if binaries == None: + exit() # Exit if not + + global snake_instance # Must be globall for further processors classes. + snake_instance = SnakeII(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.strings_section: # Print strings from __cstring section + print('Strings from __cstring section:') + print('-------------------------------') + for string in (snake_instance.getStringSection()): + print(string) + if args.all_strings: # Print strings from all sections. + print(snake_instance.findAllStringsInBinary()) + if args.save_strings: # Parse all sections, detect strings and save them to a file + extracted_strings = snake_instance.findAllStringsInBinary() + with open(args.save_strings, 'a') as f: + for s in extracted_strings: + f.write(s) + if args.info: # Print all info about the binary + print('\n<=== HEADER ===>') + print(snake_instance.getBinaryHeader()) + print('\n<=== LOAD COMMANDS ===>') + for lcd in snake_instance.getLoadCommands(): + print(lcd) + print("="*50) + print('\n<=== SEGMENTS ===>') + for segment in snake_instance.getSegments(): + print(segment) + print('\n<=== SECTIONS ===>') + for section in snake_instance.getSections(): + print(section) + print('\n<=== SYMBOLS ===>') + for symbol in snake_instance.getSymbols(): + print(symbol.name) + print('\n<=== STRINGS ===>') + print('Strings from __cstring section:') + print('-------------------------------') + for string in (snake_instance.getStringSection()): + print(string) + print('\n<=== UUID ===>') + print(f'{snake_instance.getUUID()}') + print('\n<=== ENDIANESS ===>') + print(snake_instance.getEndianess()) + print('\n<=== ENTRYPOINT ===>') + print(f'{hex(snake_instance.getMain().entrypoint)}') + + 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 +### --- II. CODE SIGNING --- ### +class CodeSigningProcessor: + def __init__(self): + 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}") +### --- 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() + + 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('--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 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() \ No newline at end of file diff --git a/II. Code Signing/python/SignatureReader.py b/II. Code Signing/python/SignatureReader.py new file mode 100755 index 0000000..0eaf2a4 --- /dev/null +++ b/II. Code Signing/python/SignatureReader.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +from asn1crypto.cms import ContentInfo +import argparse +import subprocess + +def read_file_to_bytes(filename): + '''Read a file and return its contents as bytes.''' + with open(filename, 'rb') as file: + file_contents = file.read() + return file_contents + +def loadCMS(cms_signature, human_readable=False): + '''Returns SignedData information in a human-readable or native format about the CMS Signature loaded from a file.''' + # Load the CMS signature using asn1crypto + content_info = ContentInfo.load(cms_signature) + # Access the SignedData structure + cms = content_info['content'] + + if human_readable: + openssl_cmd = ['openssl', 'cms', '-cmsout', '-print', '-inform', 'DER', '-in', args.load_cms] + result = subprocess.run(openssl_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return result.stdout.decode('utf-8') + else: + return cms.native + +def extractSignature(cms_signature, human_readable=False): + '''Return Signature from the CMS Signature''' + content_info = ContentInfo.load(cms_signature) + # Access the SignedData structure + signed_data = content_info['content'] + # Access the SignerInfo structure + signer_info = signed_data['signer_infos'][0] + # Extract the signature + signature = signer_info['signature'] + + if human_readable: + return f"0x{signature.contents.hex()}" + else: + return signature.native + +def extractPubKeyFromCert(): + openssl_cmd = ['openssl', 'x509', '-inform', 'DER', '-in', args.extract_pubkey, '-pubkey', '-noout'] + result = subprocess.run(openssl_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if result.returncode == 0: + with open('extracted_pubkey.pem', 'wb') as pubkey_file: + pubkey_file.write(result.stdout) + else: + print("Error:", result.stderr.decode('utf-8')) + +# Argument parsing +parser = argparse.ArgumentParser(description='CMS Signature Loader') +parser.add_argument('--load_cms', help="Load the DER encoded CMS Signature from the filesystem and print it", metavar='cms_signature.der') +parser.add_argument('--extract_signature', help="Extract and print the signature part from the DER encoded CMS Signature", metavar='cms_signature.der') +parser.add_argument('--extract_pubkey', help="Extract public key from the given certificate and save it to extracted_pubkey.pem", metavar='cert_0') +parser.add_argument('--human', help="Print in human-readable format", action='store_true') +args = parser.parse_args() + +if args.load_cms: + cms_signature = read_file_to_bytes(args.load_cms) + print(loadCMS(cms_signature, args.human)) + +if args.extract_signature: + cms_signature = read_file_to_bytes(args.extract_signature) + print(extractSignature(cms_signature, args.human)) + +if args.extract_pubkey: + extractPubKeyFromCert() \ No newline at end of file diff --git a/II. Code Signing/python/TrustCacheParser.py b/II. Code Signing/python/TrustCacheParser.py new file mode 100755 index 0000000..67a6017 --- /dev/null +++ b/II. Code Signing/python/TrustCacheParser.py @@ -0,0 +1,143 @@ +#!/usr/bin/env python3 +import glob +import shutil +import os +import argparse +import subprocess +from pyimg4 import * + +class TrustCacheParser: + def __init__(self, file_patterns): + self.file_patterns = file_patterns + + def copyFiles(self, destination_dir): + """ + Copy Trust Cache files to the specified destination directory. + + Parameters: + - destination_dir (str): Destination directory to copy files to. + """ + current_dir = os.getcwd() + if not destination_dir: + destination_dir = current_dir + + for file_pattern in self.file_patterns: + for file_path in glob.glob(file_pattern): + filename = os.path.basename(file_path) + new_file_path = os.path.join(destination_dir, filename) + + if os.path.exists(new_file_path): + base, ext = os.path.splitext(filename) + i = 1 + while os.path.exists(new_file_path): + new_file_path = os.path.join(destination_dir, f"{base}_{i}{ext}") + i += 1 + + shutil.copy(file_path, new_file_path) + + def parseIMG4(self): + """ + Parse Image4 files, extract payload data, and save to new files with .payload extension. + """ + current_dir = os.getcwd() + + for idx, file_pattern in enumerate(self.file_patterns[:2]): # Only BaseSystemTrustCache and StaticTrustCache + for file_path in glob.glob(file_pattern): + with open(file_path, 'rb') as infile: + img4 = IMG4(infile.read()) + + # Determine the output file path + base_name, _ = os.path.splitext(os.path.basename(file_path)) + output_name = f"{base_name}.payload" + output_path = os.path.join(current_dir, output_name) + + # Check if a file with the same name already exists in the current directory + if os.path.exists(output_path): + i = 1 + while os.path.exists(output_path): + output_name = f"{base_name}_{i}.payload" + output_path = os.path.join(current_dir, output_name) + i += 1 + + # Write the parsed data to the new file + with open(output_path, 'wb') as outfile: + outfile.write(img4.im4p.payload.output().data) + + def parseIMP4(self, imp4_path="/System/Library/Security/OSLaunchPolicyData", output_name="OSLaunchPolicyData"): + """ + Parse IMP4 file, extract payload data, and save to a new file with .payload extension. + + Parameters: + - imp4_path (str): Path to the IMP4 file. + - output_name (str): Name for the output file. + """ + output_path = os.path.join(os.getcwd(), f"{output_name}.payload") + with open(output_path, 'wb') as outfile: + with open(imp4_path, 'rb') as infile: + im4p = IM4P(infile.read()) + outfile.write(im4p.payload.output().data) + + def parseTrustCache(self): + """ + Parse Trust Cache files, run trustcache info command, and save output to .trust_cache files. + """ + current_dir = os.getcwd() + + for file_path in glob.glob(os.path.join(current_dir, '*.payload')): + output_name = f"{os.path.splitext(os.path.basename(file_path))[0]}.trust_cache" + output_path = os.path.join(current_dir, output_name) + + # Run the trustcache info command and save the output to a file + with open(output_path, 'w') as outfile: + subprocess.run(["trustcache", "info", file_path], stdout=outfile) + + def printTrustCacheContents(self): + """ + Print the contents of trust_cache files in the current directory. + """ + current_dir = os.getcwd() + + for file_path in glob.glob(os.path.join(current_dir, '*.trust_cache')): + with open(file_path, 'r') as trust_cache_file: + print(trust_cache_file.read()) + + +def main(): + parser = argparse.ArgumentParser(description="Copy Trust Cache files to a specified destination.") + parser.add_argument('--dst', '-d', required=False, help='Destination directory to copy Trust Cache files to.') + parser.add_argument('--parse_img', action='store_true', help='Parse copied Image4 to extract payload data.') + parser.add_argument('--parse_tc', action='store_true', help='Parse extract payload data to human-readable form trust cache using trustcache.') + parser.add_argument('--print_tc', action='store_true', help='Print the contents of trust_cache (files must be in the current directory and ends with .trust_cache)') + parser.add_argument('--all', action='store_true', help='parse_img -> parse_tc -> print_tc') + + args = parser.parse_args() + + file_patterns = [ + "/System/Volumes/Preboot/*/boot/*/usr/standalone/firmware/FUD/BaseSystemTrustCache.img4", + "/System/Volumes/Preboot/*/boot/*/usr/standalone/firmware/FUD/StaticTrustCache.img4", + "/System/Library/Security/OSLaunchPolicyData" # IMP4 + ] + + copy_trust_cache = TrustCacheParser(file_patterns) + + if args.dst: + copy_trust_cache.copyFiles(args.dst) + + if args.parse_img: + copy_trust_cache.parseIMG4() + copy_trust_cache.parseIMP4() + + if args.parse_tc: + copy_trust_cache.parseTrustCache() + + if args.print_tc: + copy_trust_cache.printTrustCacheContents() + + if args.all: + copy_trust_cache.parseIMG4() + copy_trust_cache.parseIMP4() + copy_trust_cache.parseTrustCache() + copy_trust_cache.printTrustCacheContents() + +if __name__ == "__main__": + main() diff --git a/README.md b/README.md index 7c19f94..ed6bb6c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ -# Snake_Apple -The code repository for the Snake&Apple article series. +# Snake & Apple +The code repository for the `Snake&Apple` article series, which documents my research about macOS security. + +Each article directory contains three subdirectories: +* `mac` - source code of macOS for references. +* `custom` - code, for example, programs written for articles. +* `python` - contains the latest CrimsonUroboros and other Python scripts created during research. ## ARTICLES ![alt](img/Snake_Apple.jpg) @@ -13,31 +18,67 @@ The code repository for the Snake&Apple article series. [CrimsonUroboros](I.%20Mach-O/python/CrimsonUroboros.py) - 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 ```console -usage: CrimsonUroboros.py [-h] -p PATH [--file_type] [--header_flags] [--endian] [--header] [--load_commands] [--segments] [--sections] [--symbols] [--chained_fixups] [--exports_trie] - [--uuid] [--main] [--strings_section] [--all_strings] [--save_strings SAVE_STRINGS] [--info] +usage: CrimsonUroboros [-h] -p PATH [--file_type] [--header_flags] [--endian] + [--header] [--load_commands] [--segments] [--sections] + [--symbols] [--chained_fixups] [--exports_trie] [--uuid] + [--main] [--strings_section] [--all_strings] + [--save_strings all_strings.txt] [--info] + [--verify_signature] [--cd_info] [--cd_requirements] + [--entitlements [human|xml|var]] + [--extract_cms cms_signature.der] + [--extract_certificates certificate_name] + [--remove_sig unsigned_binary] + [--sign_binary [adhoc|identity_number]] -Mach-O files parser for binary analysis. +Mach-O files parser for binary analysis options: -h, --help show this help message and exit - -p PATH, --path PATH Path to the Mach-O file. - --file_type Print binary file type. - --header_flags Print binary header flags. - --endian Print binary endianess. - --header Print binary header. - --load_commands Print binary load commands names. - --segments Print binary segments in human friendly form. - --sections Print binary sections in human friendly form. - --symbols Print all binary symbols. - --chained_fixups Print Chained Fixups information. - --exports_trie Print Export Trie information. - --uuid Print UUID. - --main Print entry point and stack size. - --strings_section Print strings from __cstring section. - --all_strings Print strings from all sections. - --save_strings SAVE_STRINGS - Parse all sections, detect strings and save them to a file. - --info Print header, load commands, segments, sections, symbols and strings. + -p PATH, --path PATH Path to the Mach-O file + +MACH-O ARGS: + --file_type Print binary file type + --header_flags Print binary header flags + --endian Print binary endianess + --header Print binary header + --load_commands Print binary load commands names + --segments Print binary segments in human-friendly form + --sections Print binary sections in human-friendly form + --symbols Print all binary symbols + --chained_fixups Print Chained Fixups information + --exports_trie Print Export Trie information + --uuid Print UUID + --main Print entry point and stack size + --strings_section Print strings from __cstring section + --all_strings Print strings from all sections + --save_strings all_strings.txt + Parse all sections, detect strings, and save them to a + file + --info Print header, load commands, segments, sections, + symbols, and strings + +CODE SIGNING ARGS: + --verify_signature Code Signature verification (if the contents of the + binary have been modified) + --cd_info Print Code Signature information + --cd_requirements Print Code Signature Requirements + --entitlements [human|xml|var] + Print Entitlements in a human-readable, XML, or DER + format (default: human) + --extract_cms cms_signature.der + Extract CMS Signature from the Code Signature and save + it to a given file + --extract_certificates certificate_name + 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 + --remove_sig unsigned_binary + Save the new file on a disk with removed signature + --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 @@ -56,5 +97,81 @@ DYLIB:/Users/karmaz95/t/dylibs/use_dylib_app/customs/custom.dylib BUNDLE:/Users/karmaz95/t/bundles/MyBundle ``` +[TrustCacheParser](II.%20Code%20Signing/python/TrustCacheParser.py) - designed to parse trust caches and print it in human readable form (based on [PyIMG4](https://github.com/m1stadev/PyIMG4) and [trustcache](https://github.com/CRKatri/trustcache)) +* Usage: +```console +usage: TrustCacheParser [-h] [--dst DST] [--parse_img] [--parse_tc] [--print_tc] [--all] + +Copy Trust Cache files to a specified destination. + +options: + -h, --help show this help message and exit + --dst DST, -d DST Destination directory to copy Trust Cache files to. + --parse_img Parse copied Image4 to extract payload data. + --parse_tc Parse extract payload data to human-readable form trust cache using + trustcache. + --print_tc Print the contents of trust_cache (files must be in the current + directory and ends with .trust_cache) + --all parse_img -> parse_tc -> print_tc +``` +[SignatureReader](II.%20Code%20Signing/python/SignatureReader.py) - designed to parse extracted cms sginature from Mach-O files. +* Usage: +```bash +# First extract CMS Signature using CrimsonUroboros +CrimsonUroboros -p target_binary --extract_cms cms_sign +# or using extract_cms.sh script +./extract_cms.sh target_binary cms_sign +``` + +```console +usage: SignatureReader [-h] [--load_cms cms_signature.der] + [--extract_signature cms_signature.der] + [--extract_pubkey cert_0] [--human] + +CMS Signature Loader + +options: + -h, --help show this help message and exit + --load_cms cms_signature.der + Load the DER encoded CMS Signature from the filesystem + and print it + --extract_signature cms_signature.der + Extract and print the signature part from the DER + encoded CMS Signature + --extract_pubkey cert_0 + Extract public key from the given certificate and save + it to extracted_pubkey.pem + --human Print in human-readable format +❯ CrimsonUroboros -p signed_ad_hoc_example --extract_cms cms_sign +``` +* Example: +```bash +SignatureReader --extract_signature cms_sign --human +0x25ca80ad5f11be197dc7a2d53f3db5b6bf463a38224db8c0a17fa4b8fd5ad7e0c60f2be8e8849cf2e581272290991c0db40b0d452b2d2dbf230c0ccab3a6d78e0230bca7bccbc50d379372bcddd8d8542add5ec59180bc3409b2df3bd8995301b9ba1e65ac62420c75104f12cb58b430fde8a177a1cd03940d4b0e77a9d875d65552cf96f03cb63b437c36d9bab12fa727e17603da49fcb870edaec115f90def1ac2ad12c2e9349a5470b5ed2f242b5566cd7ddee785eff8ae5484f145a8464d4dc3891b10a3b2981e9add1e4c0aec31fa80320eb5494d9623400753adf24106efdd07ad657035ed2876e9460219944a4730b0b620954961350ddb1fcf0ea539 +``` + +[extract_cms.sh](II.%20Code%20Signing/custom/extract_cms.sh) - designed to extract cms sginature from Mach-O files (bash alternative to `SingatureReader --extract_signature`). +* Example: +``` +./extract_cms.sh target_binary cms_sign +``` + +## INSTALL +``` +pip -r requirements.txt +python3 -m pip install pyimg4 +wget https://github.com/CRKatri/trustcache/releases/download/v2.0/trustcache_macos_arm64 -O /usr/local/bin/trustcache +chmod +x /usr/local/bin/trustcache +xattr -d com.apple.quarantine /usr/local/bin/trustcache +``` + +## LIMITATIONS +* Codesigning module(codesign wrapper) works only on macOS. + ## WHY UROBOROS? I will write the code for each article as a class SnakeX, where X will be the article number. To make it easier for the audience to follow. Each Snake class will be a child of the previous one and infinitely "eat itself" (inherit methods of the previous class), like Uroboros. + +## 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 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..26def5f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,10 @@ +lief +uuid +argparse +subprocess +os +sys +asn1crypto +glob +shutil +pyimg4 \ No newline at end of file