mirror of
https://github.com/Karmaz95/Snake_Apple.git
synced 2026-04-11 14:52:03 +02:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5044a68d2e | ||
|
|
dec6d69edc | ||
|
|
6073ce1ae6 | ||
|
|
68fcd39bd8 | ||
|
|
27758c83d3 | ||
|
|
59d22fde27 |
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
**/.DS_Store
|
||||
**/.vscode
|
||||
18
II. Code Signing/custom/extract_cms.sh
Executable file
18
II. Code Signing/custom/extract_cms.sh
Executable 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
|
||||
399
II. Code Signing/mac/CSCommon.h
Normal file
399
II. Code Signing/mac/CSCommon.h
Normal 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
|
||||
317
II. Code Signing/mac/cs_blobs.h
Normal file
317
II. Code Signing/mac/cs_blobs.h
Normal 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 */
|
||||
85
II. Code Signing/mac/trustcache.h
Normal file
85
II. Code Signing/mac/trustcache.h
Normal 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 */
|
||||
421
II. Code Signing/python/CrimsonUroboros.py
Executable file
421
II. Code Signing/python/CrimsonUroboros.py
Executable 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()
|
||||
67
II. Code Signing/python/SignatureReader.py
Executable file
67
II. Code Signing/python/SignatureReader.py
Executable 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()
|
||||
143
II. Code Signing/python/TrustCacheParser.py
Executable file
143
II. Code Signing/python/TrustCacheParser.py
Executable 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()
|
||||
7
III. Checksec/custom/aslr_test.c
Normal file
7
III. Checksec/custom/aslr_test.c
Normal file
@@ -0,0 +1,7 @@
|
||||
#include<stdio.h>
|
||||
|
||||
int main(){
|
||||
printf("main address : %p\n",&main);
|
||||
printf("printf address : %p\n",&printf);
|
||||
return 0;
|
||||
}
|
||||
9
III. Checksec/custom/buffer_overflow.c
Normal file
9
III. Checksec/custom/buffer_overflow.c
Normal 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;
|
||||
}
|
||||
20
III. Checksec/custom/example.m
Normal file
20
III. Checksec/custom/example.m
Normal 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;
|
||||
}
|
||||
23
III. Checksec/custom/heap_example.c
Normal file
23
III. Checksec/custom/heap_example.c
Normal 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;
|
||||
}
|
||||
|
||||
6
III. Checksec/custom/hello.c
Normal file
6
III. Checksec/custom/hello.c
Normal file
@@ -0,0 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello, World!\n");
|
||||
return 0;
|
||||
}
|
||||
2537
III. Checksec/mac/Header.cpp
Normal file
2537
III. Checksec/mac/Header.cpp
Normal file
File diff suppressed because it is too large
Load Diff
361
III. Checksec/mac/SecCode.cpp
Normal file
361
III. Checksec/mac/SecCode.cpp
Normal 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
1590
III. Checksec/mac/loader.h
Normal file
File diff suppressed because it is too large
Load Diff
720
III. Checksec/python/CrimsonUroboros.py
Executable file
720
III. Checksec/python/CrimsonUroboros.py
Executable 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
106
III. Checksec/python/LCFinder.py
Executable 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()
|
||||
116
III. Checksec/python/ModifyMachOFlags.py
Executable file
116
III. Checksec/python/ModifyMachOFlags.py
Executable 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
236
README.md
@@ -1,49 +1,93 @@
|
||||
# Snake_Apple
|
||||
The code repository for the Snake&Apple article series.
|
||||
# Snake & Apple
|
||||
The code repository for the `Snake&Apple` article series, which documents my research about macOS security.
|
||||
|
||||
Each article directory contains three subdirectories:
|
||||
* `mac` - source code of macOS for references.
|
||||
* `custom` - code, for example, programs written for articles.
|
||||
* `python` - contains the latest CrimsonUroboros and other Python scripts created during research.
|
||||
|
||||
## ARTICLES
|
||||

|
||||
* ☑ [I. Mach-O](https://medium.com/p/a8eda4b87263)
|
||||
* ☐ [II. Code Signing]()
|
||||
* ☐ [III. Checksec]()
|
||||
* ☑ [I. Mach-O](https://karol-mazurek95.medium.com/snake-apple-i-mach-o-a8eda4b87263?sk=v2%2Ffc1cbfa4-e2d4-4387-9a82-b27191978b5b)
|
||||
* ☑ [II. Code Signing](https://karol-mazurek95.medium.com/snake-apple-ii-code-signing-f0a9967b7f02?sk=v2%2Fbbc87007-89ca-4135-91d6-668b5d2fe9ae)
|
||||
* ☑ [III. Checksec](https://karol-mazurek95.medium.com/snake-apple-iii-checksec-ed64a4b766c1?sk=v2%2Fb4b8d637-e906-4b6b-8088-ca1f893cd787)
|
||||
* ☐ [IV. Dylibs]()
|
||||
|
||||
## TOOLS
|
||||
### [CrimsonUroboros](III.%20Checksec/python/CrimsonUroboros.py)
|
||||

|
||||
[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
10
requirements.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
lief
|
||||
uuid
|
||||
argparse
|
||||
subprocess
|
||||
os
|
||||
sys
|
||||
asn1crypto
|
||||
glob
|
||||
shutil
|
||||
pyimg4
|
||||
Reference in New Issue
Block a user