6 Commits
v0.1 ... v0.3

Author SHA1 Message Date
Karmaz95
5044a68d2e 2024-01-07 13:52:07 +01:00
Karmaz95
dec6d69edc 2024-01-06 22:06:00 +01:00
Karmaz95
6073ce1ae6 2024-01-03 15:40:37 +01:00
Karmaz95
68fcd39bd8 2024-01-02 22:10:58 +01:00
Karmaz95
27758c83d3 2024-01-02 22:08:42 +01:00
Karmaz95
59d22fde27 2024-01-02 22:02:39 +01:00
21 changed files with 7165 additions and 28 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
**/.DS_Store
**/.vscode

View File

@@ -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

View File

@@ -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 <CoreFoundation/CoreFoundation.h>
#include <TargetConditionals.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/*
* 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

View File

@@ -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 <stdint.h>
#include <string.h>
/* 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 */

View File

@@ -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 <stdint.h>
#include <kern/cs_blobs.h>
#include <uuid/uuid.h>
/* 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 */

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -0,0 +1,7 @@
#include<stdio.h>
int main(){
printf("main address : %p\n",&main);
printf("printf address : %p\n",&printf);
return 0;
}

View File

@@ -0,0 +1,9 @@
#include <stdio.h>
#include <string.h>
int main() {
char buffer[10];
strcpy(buffer, "Hello, World!"); // Vulnerable operation
printf("You entered: %s\n", buffer);
return 0;
}

View File

@@ -0,0 +1,20 @@
#import <Foundation/Foundation.h>
@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;
}

View File

@@ -0,0 +1,23 @@
#include <stdio.h>
#include <stdlib.h>
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;
}

View File

@@ -0,0 +1,6 @@
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}

2537
III. Checksec/mac/Header.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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 <security_utilities/cfmunge.h>
#include <security_utilities/logging.h>
#include <xpc/private.h>
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<CFMutableDictionaryRef> 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<SecStaticCode> 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<DiskRep> 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<SecCode> 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<CFDictionaryRef>("{%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<CFDataRef> auditData = makeCFData(audit, sizeof(audit_token_t));
if (SecCode *guest = KernelCode::active()->locateGuest(CFTemp<CFDictionaryRef>("{%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<SecCode> 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<SecStaticCode> code = SecStaticCode::requiredStatic(codeRef);
CFRef<CFDictionaryRef> info = code->signingInformation(flags);
if (flags & kSecCSDynamicInformation)
if (SecPointer<SecCode> dcode = SecStaticCode::optionalDynamic(codeRef))
info.take(cfmake<CFDictionaryRef>("{+%O,%O=%u}", info.get(), kSecCodeInfoStatus, dcode->status()));
CodeSigning::Required(infoRef) = info.yield();
END_CSAPI
}

1590
III. Checksec/mac/loader.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -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()

106
III. Checksec/python/LCFinder.py Executable file
View File

@@ -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()

View File

@@ -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()

236
README.md
View File

@@ -1,49 +1,93 @@
# Snake_Apple
The code repository for the Snake&amp;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)
* &#9745; [I. Mach-O](https://medium.com/p/a8eda4b87263)
* &#9744; [II. Code Signing]()
* &#9744; [III. Checksec]()
* &#9745; [I. Mach-O](https://karol-mazurek95.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b)
* &#9745; [II. Code Signing](https://karol-mazurek95.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae)
* &#9745; [III. Checksec](https://karol-mazurek95.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787)
* &#9744; [IV. Dylibs]()
## TOOLS
### [CrimsonUroboros](III.%20Checksec/python/CrimsonUroboros.py)
![alt](img/CrimsonUroboros.jpg)
[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.
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
CrimsonUroboros.py -p PATH --info
```
[MachOFileFinder](I.%20Mach-O/python/MachOFileFinder.py) - designed to find ARM64 Mach-O binaries within a specified directory and print their file type.
***
### [MachOFileFinder](I.%20Mach-O/python/MachOFileFinder.py)
Designed to find ARM64 Mach-O binaries within a specified directory and print their file type.
* Usage:
```bash
python MachOFileFinder.py PATH
@@ -55,6 +99,142 @@ EXECUTE:/Users/karmaz95/t/pingsender
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
```
***
### [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
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)
## 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.

10
requirements.txt Normal file
View File

@@ -0,0 +1,10 @@
lief
uuid
argparse
subprocess
os
sys
asn1crypto
glob
shutil
pyimg4