dynamic code signing

for processes, use dynamic code signing APIs
This commit is contained in:
Patrick Wardle
2018-07-19 10:00:33 +03:00
parent 972241fae2
commit 46c18bf01d
13 changed files with 479 additions and 1650 deletions
BIN
View File
Binary file not shown.
+27 -31
View File
@@ -19,7 +19,6 @@
@class Binary;
@class Process;
/* DEFINES */
//from audit_kevents.h
@@ -29,30 +28,29 @@
#define EVENT_EXEC 27
#define EVENT_SPAWN 43190
//signers
enum Signer{None, Apple, AppStore, DevID, AdHoc};
//signature status
#define KEY_SIGNATURE_STATUS @"signatureStatus"
//signer
#define KEY_SIGNATURE_SIGNER @"signatureSigner"
//signing auths
#define KEY_SIGNING_AUTHORITIES @"signingAuthorities"
#define KEY_SIGNATURE_AUTHORITIES @"signatureAuthorities"
//code signing id
#define KEY_SIGNATURE_IDENTIFIER @"signingIdentifier"
#define KEY_SIGNATURE_IDENTIFIER @"signatureIdentifier"
//file belongs to apple?
#define KEY_SIGNING_IS_APPLE @"signedByApple"
//file signed with apple dev id
#define KEY_SIGNING_IS_APPLE_DEV_ID @"signedWithDevID"
//from app store
#define KEY_SIGNING_IS_APP_STORE @"fromAppStore"
//entitlements
#define KEY_SIGNATURE_ENTITLEMENTS @"signatureEntitlements"
/* TYPEDEFS */
//block for library
typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
/* OBJECT: PROCESS INFO */
@interface ProcInfo : NSObject
@@ -103,6 +101,9 @@ typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
//ancestors
@property(nonatomic, retain)NSMutableArray* _Nonnull ancestors;
//signing info
@property(nonatomic, retain)NSMutableDictionary* _Nonnull signingInfo;
//Binary object
// has path, hash, etc
@property(nonatomic, retain)Binary* _Nonnull binary;
@@ -116,13 +117,18 @@ typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
// method will then (try) fill out rest of object
-(id _Nullable)init:(pid_t)processID;
//generate signing info
// also classifies if Apple/from App Store/etc.
-(void)generateSigningInfo:(SecCSFlags)flags;
//set process's path
-(void)pathFromPid;
//generate list of ancestors
-(void)enumerateAncestors;
//class method to get parent of arbitrary process
//class method
// get's parent of arbitrary process
+(pid_t)getParentID:(pid_t)child;
@end
@@ -158,22 +164,13 @@ typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
//signing info
@property(nonatomic, retain)NSDictionary* _Nonnull signingInfo;
//entitlements
@property(nonatomic, retain)NSDictionary* _Nonnull entitlements;
//hash
@property(nonatomic, retain)NSString* _Nonnull sha256;
@property(nonatomic, retain)NSMutableString* _Nonnull sha256;
//identifier
// either signing id or sha256 hash
@property(nonatomic, retain)NSString* _Nonnull identifier;
//flag indicating binary belongs to Apple OS
@property BOOL isApple;
//flag indicating binary is from official App Store
@property BOOL isAppStore;
/* METHODS */
//init w/ a path
@@ -187,20 +184,19 @@ typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
// for apps, this will be app's icon, otherwise just a standard system one
-(void)getIcon;
//generate signing info
// also classifies if Apple/from App Store/etc.
-(void)generateSigningInfo:(SecCSFlags)flags entitlements:(BOOL)entitlements;
//generate signing info (statically)
-(void)generateSigningInfo:(SecCSFlags)flags;
//generate entitlements
// note: can also call 'generateSigningInfo' w/ 'entitlements:YES'
-(void)generateEntitlements;
/* the following methods are not invoked automatically
as such, if you code has to manually invoke them if you want this info
*/
//generate hash
// algo: sha256
-(void)generateHash;
//generate id
// eithersigning id, or sha256 hash
// note: will generate signing info if needed
// either signing id, or sha256 hash
-(void)generateIdentifier;
@end
+5 -9
View File
@@ -15,8 +15,6 @@
7DD0A9351F35A04B000EA15D /* Signing.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DD0A9271F35A04B000EA15D /* Signing.m */; };
7DD0A9361F35A04B000EA15D /* Signing.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DD0A9281F35A04B000EA15D /* Signing.h */; };
7DD0A9371F35A04B000EA15D /* Consts.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DD0A9291F35A04B000EA15D /* Consts.h */; };
7DD0A9381F35A04B000EA15D /* AppReceipt.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DD0A92A1F35A04B000EA15D /* AppReceipt.h */; };
7DD0A9391F35A04B000EA15D /* AppReceipt.m in Sources */ = {isa = PBXBuildFile; fileRef = 7DD0A92B1F35A04B000EA15D /* AppReceipt.m */; };
7DD0A9471F37F373000EA15D /* procInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 7DD0A9461F37F373000EA15D /* procInfo.h */; };
/* End PBXBuildFile section */
@@ -30,8 +28,6 @@
7DD0A9271F35A04B000EA15D /* Signing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Signing.m; path = procInfo/Signing.m; sourceTree = SOURCE_ROOT; };
7DD0A9281F35A04B000EA15D /* Signing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Signing.h; path = procInfo/Signing.h; sourceTree = SOURCE_ROOT; };
7DD0A9291F35A04B000EA15D /* Consts.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Consts.h; path = procInfo/Consts.h; sourceTree = SOURCE_ROOT; };
7DD0A92A1F35A04B000EA15D /* AppReceipt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppReceipt.h; path = procInfo/AppReceipt.h; sourceTree = SOURCE_ROOT; };
7DD0A92B1F35A04B000EA15D /* AppReceipt.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppReceipt.m; path = procInfo/AppReceipt.m; sourceTree = SOURCE_ROOT; };
7DD0A9461F37F373000EA15D /* procInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = procInfo.h; path = procInfo/procInfo.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -75,8 +71,6 @@
7D83A9E61EB6465D001506F0 /* src */ = {
isa = PBXGroup;
children = (
7DD0A92A1F35A04B000EA15D /* AppReceipt.h */,
7DD0A92B1F35A04B000EA15D /* AppReceipt.m */,
7DD0A9211F35A04B000EA15D /* Binary.m */,
7DD0A9291F35A04B000EA15D /* Consts.h */,
7DD0A9221F35A04B000EA15D /* Process.m */,
@@ -97,7 +91,6 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
7DD0A9381F35A04B000EA15D /* AppReceipt.h in Headers */,
7DD0A9331F35A04B000EA15D /* Utilities.h in Headers */,
7DD0A9471F37F373000EA15D /* procInfo.h in Headers */,
7DD0A9371F35A04B000EA15D /* Consts.h in Headers */,
@@ -131,7 +124,7 @@
7D83A9DC1EB6465D001506F0 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0900;
LastUpgradeCheck = 0940;
ORGANIZATIONNAME = "Objective-See";
TargetAttributes = {
7D83A9E31EB6465D001506F0 = {
@@ -183,7 +176,6 @@
7DD0A92F1F35A04B000EA15D /* Binary.m in Sources */,
7DD0A92D1F35A04B000EA15D /* ProcessMonitor.m in Sources */,
7DD0A9321F35A04B000EA15D /* Utilities.m in Sources */,
7DD0A9391F35A04B000EA15D /* AppReceipt.m in Sources */,
7DD0A9351F35A04B000EA15D /* Signing.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -204,6 +196,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -211,6 +204,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
@@ -257,6 +251,7 @@
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
@@ -264,6 +259,7 @@
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
-132
View File
@@ -1,132 +0,0 @@
//
// File: AppReceipt.h
// Project: Proc Info
//
// Created by: Patrick Wardle
// Copyright: 2017 Objective-See
// License: Creative Commons Attribution-NonCommercial 4.0 International License
//
// note a: see https://objective-see.com/blog/blog_0x10.html for details
// note b: this code inspired by https://gist.github.com/sazameki/3026845
//
#ifndef AppReceipt_h
#define AppReceipt_h
#import <Security/CMSDecoder.h>
#import <Foundation/Foundation.h>
#import <Security/SecAsn1Coder.h>
#import <CommonCrypto/CommonDigest.h>
#import <Security/SecAsn1Templates.h>
//from 'Receipt Fields' section (Apple's 'Receipt Validation Programming Guide')
//ANS.1 data struct
typedef struct
{
size_t length;
unsigned char *data;
} ASN1_Data;
//receipt attribute struct
typedef struct
{
ASN1_Data type; // INTEGER
ASN1_Data version; // INTEGER
ASN1_Data value; // OCTET STRING
} ReceiptAttribute;
//receipt payload struct
typedef struct
{
ReceiptAttribute **attrs;
} ReceiptPayload;
//ASN.1 receipt attribute template (from [1])
static const SecAsn1Template kReceiptAttributeTemplate[] =
{
{ SEC_ASN1_SEQUENCE, 0, NULL, sizeof(ReceiptAttribute) },
{ SEC_ASN1_INTEGER, offsetof(ReceiptAttribute, type), NULL, 0 },
{ SEC_ASN1_INTEGER, offsetof(ReceiptAttribute, version), NULL, 0 },
{ SEC_ASN1_OCTET_STRING, offsetof(ReceiptAttribute, value), NULL, 0 },
{ 0, 0, NULL, 0 }
};
//ASN.1 receipt template set (from [1])
static const SecAsn1Template kSetOfReceiptAttributeTemplate[] =
{
{ SEC_ASN1_SET_OF, 0, kReceiptAttributeTemplate, sizeof(ReceiptPayload) },
{ 0, 0, NULL, 0 }
};
//attribute type for bundle ID
#define RECEIPT_ATTR_BUNDLE_ID 2
//attribute type for app version
#define RECEIPT_ATTR_APP_VERSION 3
//attribute type for opaque value
#define RECEIPT_ATTR_OPAQUE_VALUE 4
//attribute type for receipt's hash
#define RECEIPT_ATTR_RECEIPT_HASH 5
//key for bundle id
#define KEY_BUNDLE_ID @"bundleID"
//key for bundle id data
#define KEY_BUNDLE_DATA @"bundleIDData"
//key for app version
#define KEY_APP_VERSION @"applicationVersion"
//key for opaque value
#define KEY_OPAQUE_VALUE @"opaqueValue"
//key for receipt's sha-1 hash
#define KEY_RECEIPT_HASH @"receiptHash"
//class interface
@interface AppReceipt : NSObject
{
}
/* METHODS */
//init with app path
// locate/load receipt, etc
-(instancetype)init:(NSBundle *)bundle;
/* PROPERTIES */
//encoded receipt data
@property (nonatomic, retain) NSData* encodedData;
//decoded receipt data
@property (nonatomic, retain) NSData* decodedData;
//receipt components
@property(nonatomic, retain)NSMutableDictionary* components;
//bundle id (from receipt)
@property (nonatomic, strong, readonly) NSString *bundleIdentifier;
//bundle id data (from receipt)
@property (nonatomic, strong, readonly) NSData *bundleIdentifierData;
//app version (from receipt)
@property (nonatomic, strong, readonly) NSString *appVersion;
//opaque value (from receipt)
@property (nonatomic, strong, readonly) NSData *opaqueValue;
//receipts hash (from receipt)
@property (nonatomic, strong, readonly) NSData *receiptHash;
@end
#endif /* AppReceipt_h */
-403
View File
@@ -1,403 +0,0 @@
//
// File: AppReceipt.h
// Project: Proc Info
//
// Created by: Patrick Wardle
// Copyright: 2017 Objective-See
// License: Creative Commons Attribution-NonCommercial 4.0 International License
//
// note a: see https://objective-see.com/blog/blog_0x10.html for details
// note b: this code inspired by [1] https://gist.github.com/sazameki/3026845
//
#import "AppReceipt.h"
//helper function from [1]
// extract an int from ASN.1 data
inline static int getIntValueFromASN1Data(const ASN1_Data *asn1Data)
{
int ret = 0;
for (int i = 0; i < asn1Data->length; i++)
{
ret = (ret << 8) | asn1Data->data[i];
}
return ret;
}
//helper function from [1]
// decode string from ASN.1 data
inline static NSString *decodeUTF8StringFromASN1Data(SecAsn1CoderRef decoder, ASN1_Data srcData)
{
//data struct
ASN1_Data asn1Data = {0};
//decoded string
NSString* decodedString = nil;
//status
OSStatus status = -1;
//decode
status = SecAsn1Decode(decoder, srcData.data, srcData.length, kSecAsn1UTF8StringTemplate, &asn1Data);
if(noErr != status)
{
//bail
goto bail;
}
//convert to string
decodedString = [[NSString alloc] initWithBytes:asn1Data.data length:asn1Data.length encoding:NSUTF8StringEncoding];
bail:
return decodedString;
}
//class implementation
@implementation AppReceipt
@synthesize components;
@synthesize encodedData;
@synthesize decodedData;
//init with app path
// locate/load/decode receipt, etc
-(instancetype)init:(NSBundle *)bundle
{
//init
if(self = [super init])
{
//first check for receipt
if( (nil == bundle.appStoreReceiptURL) ||
(YES != [[NSFileManager defaultManager] fileExistsAtPath:bundle.appStoreReceiptURL.path]) )
{
//unset
self = nil;
//bail
goto bail;
}
//load encoded receipt data
self.encodedData = [NSData dataWithContentsOfURL:bundle.appStoreReceiptURL];
if(nil == self.encodedData)
{
//unset
self = nil;
//bail
goto bail;
}
//decode receipt data
self.decodedData = [self decode];
if(nil == self.decodedData)
{
//unset
self = nil;
//bail
goto bail;
}
//parse out values
// bundle id, app version, etc
self.components = [self parse];
if( (nil == self.components) ||
(0 == self.components.count) )
{
//unset
self = nil;
//bail
goto bail;
}
}
bail:
return self;
}
//decode receipt data
// some validations performed here too
-(NSData*)decode
{
//decoded data
NSData* decoded = nil;
//decoder
CMSDecoderRef decoder = NULL;
//policy
SecPolicyRef policy = NULL;
//trust
SecTrustRef trust = NULL;
//status
OSStatus status = -1;
//data
CFDataRef data = NULL;
//number of signers
size_t signers = 0;
//signer status
CMSSignerStatus signerStatus = -1;
//cert verify
OSStatus certVerifyResult = 1;
//create decoder
status = CMSDecoderCreate(&decoder);
if(noErr != status)
{
//bail
goto bail;
}
//add encoded data to message
status = CMSDecoderUpdateMessage(decoder, self.encodedData.bytes, self.encodedData.length);
if(noErr != status)
{
//bail
goto bail;
}
//decode
status = CMSDecoderFinalizeMessage(decoder);
if(noErr != status)
{
//bail
goto bail;
}
//create policy
policy = SecPolicyCreateBasicX509();
if(NULL == policy)
{
//bail
goto bail;
}
//CHECK 1:
// make sure there is a signer
status = CMSDecoderGetNumSigners(decoder, &signers);
if( (noErr != status) ||
(0 == signers) )
{
//bail
goto bail;
}
//CHECK 2:
// make sure signer status is ok
status = CMSDecoderCopySignerStatus(decoder, 0, policy, TRUE, &signerStatus, &trust, &certVerifyResult);
if( (noErr != status) ||
(kCMSSignerValid != signerStatus) )
{
//bail
goto bail;
}
//grab decoded content
status = CMSDecoderCopyContent(decoder, &data);
if(noErr != status)
{
//bail
goto bail;
}
//convert to NSData
decoded = [NSData dataWithData:(__bridge NSData * _Nonnull)(data)];
bail:
//release policy
if(NULL != policy)
{
//release
CFRelease(policy);
//unset
policy = NULL;
}
//release trust
if(NULL != trust)
{
//release
CFRelease(trust);
//unset
trust = NULL;
}
//release decoder
if(NULL != decoder)
{
//release
CFRelease(decoder);
//unset
decoder = NULL;
}
//release data
if(NULL != data)
{
//release
CFRelease(data);
//unset
data = NULL;
}
return decoded;
}
//parse decoded receipt
// extract out items such as bundle id, app version, etc.
-(NSMutableDictionary*)parse
{
//decoder
SecAsn1CoderRef decoder = NULL;
//status
OSStatus status = -1;
//payload struct
ReceiptPayload payload = {0};
//attribute
ReceiptAttribute *attribute;
//dictionary for items
NSMutableDictionary* items = nil;
//create decoder
status = SecAsn1CoderCreate(&decoder);
if(noErr != status)
{
//bail
goto bail;
}
//decode
status = SecAsn1Decode(decoder, self.decodedData.bytes, self.decodedData.length, kSetOfReceiptAttributeTemplate, &payload);
if(noErr != status)
{
//bail
goto bail;
}
//init dictionary for items
items = [NSMutableDictionary dictionary];
//extact attributes
// save those of interest
for(int i = 0; (attribute = payload.attrs[i]); i++)
{
//process each type
switch(getIntValueFromASN1Data(&attribute->type))
{
//bundle id
// save bundle id and data
case RECEIPT_ATTR_BUNDLE_ID:
{
//save bundle id
items[KEY_BUNDLE_ID] = decodeUTF8StringFromASN1Data(decoder, attribute->value);
//save bundle id data
items[KEY_BUNDLE_DATA] = [NSData dataWithBytes:attribute->value.data length:attribute->value.length];
break;
}
//app version
case RECEIPT_ATTR_APP_VERSION:
{
//save
items[KEY_APP_VERSION] = decodeUTF8StringFromASN1Data(decoder, attribute->value);
break;
}
//opaque value
case RECEIPT_ATTR_OPAQUE_VALUE:
{
//save
items[KEY_OPAQUE_VALUE] = [NSData dataWithBytes:attribute->value.data length:attribute->value.length];
break;
}
//receipt hash
case RECEIPT_ATTR_RECEIPT_HASH:
{
//save
items[KEY_RECEIPT_HASH] = [NSData dataWithBytes:attribute->value.data length:attribute->value.length];
break;
}
//default
// ignore
default:
{
break;
}
}//switch
}//for all attributes
bail:
//release decoder
if(NULL != decoder)
{
//release
SecAsn1CoderRelease(decoder);
//unset
decoder = NULL;
}
return items;
}
//return bundle id
-(NSString*)bundleIdentifier
{
return self.components[KEY_BUNDLE_ID];
}
//return bundle id data
-(NSData*)bundleIdentifierData
{
return self.components[KEY_BUNDLE_DATA];
}
//return app version
-(NSString*)appVersion
{
return self.components[KEY_APP_VERSION];
}
//return opaque data
-(NSData*)opaqueValue
{
return self.components[KEY_OPAQUE_VALUE];
}
//return receipt hash
-(NSData*)receiptHash
{
return self.components[KEY_RECEIPT_HASH];
}
@end
+58 -70
View File
@@ -12,19 +12,16 @@
#import "procInfo.h"
#import "Utilities.h"
#import <CommonCrypto/CommonDigest.h>
@implementation Binary
@synthesize icon;
@synthesize name;
@synthesize path;
@synthesize bundle;
@synthesize isApple;
@synthesize metadata;
@synthesize attributes;
@synthesize identifier;
@synthesize isAppStore;
@synthesize signingInfo;
@synthesize entitlements;
//init binary object
// note: CPU-intensive logic (code signing, etc) called manually
@@ -151,17 +148,24 @@
bail:
//release names
if(nil != attributeNames)
if(NULL != attributeNames)
{
//release
CFRelease(attributeNames);
//unset
attributeNames = NULL;
}
//release item
if(nil != mdItem)
//release md item
if(NULL != mdItem)
{
//release
CFRelease(mdItem);
//unset
mdItem = NULL;
}
return;
@@ -180,9 +184,6 @@ bail:
//icon's path extension
NSString* iconExtension = nil;
//system's document icon
static NSData* documentIcon = nil;
//skip 'short' paths
// otherwise system logs an error
if( (YES != [self.path hasPrefix:@"/"]) &&
@@ -224,24 +225,6 @@ bail:
{
//extract icon
self.icon = [[NSWorkspace sharedWorkspace] iconForFile:self.path];
//load system document icon
// static var, so only load once
if(nil == documentIcon)
{
//load
documentIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:
NSFileTypeForHFSTypeCode(kGenericDocumentIcon)] TIFFRepresentation];
}
//if 'iconForFile' method doesn't find and icon, it returns the system 'document' icon
// the system 'applicaiton' icon seems more applicable, so use that here...
if(YES == [[self.icon TIFFRepresentation] isEqual:documentIcon])
{
//set icon to system 'applicaiton' icon
self.icon = [[NSWorkspace sharedWorkspace]
iconForFileType: NSFileTypeForHFSTypeCode(kGenericApplicationIcon)];
}
}
//make standard size...
@@ -252,39 +235,47 @@ bail:
return;
}
//generate signing info
// also classifies if Apple/from App Store/etc.
-(void)generateSigningInfo:(SecCSFlags)flags entitlements:(BOOL)entitlements
//statically, generate signing info
-(void)generateSigningInfo:(SecCSFlags)flags
{
//extract signing info (do this first!)
// from Apple, App Store, signing authorities, etc
self.signingInfo = extractSigningInfo(self.path, flags, entitlements);
//perform more signing checks and lists
// gotta be happily signed for checks though
if(0 == [self.signingInfo[KEY_SIGNATURE_STATUS] intValue])
{
//set flag for signed by Apple proper
self.isApple = [self.signingInfo[KEY_SIGNING_IS_APPLE] boolValue];
//when not Apple proper
// check flag for from official App Store or is whitelisted
if(YES != isApple)
{
//set flag
self.isAppStore = [self.signingInfo[KEY_SIGNING_IS_APP_STORE] boolValue];
}
}
//extract signing info statically
self.signingInfo = extractSigningInfo(0, self.path, flags);
return;
}
//generate hash
-(void)generateHash
{
//hash
self.sha256 = PI_hashFile(self.path);
//file's contents
NSData* fileContents = nil;
//hash digest
uint8_t digestSHA256[CC_SHA256_DIGEST_LENGTH] = {0};
//load file
if(nil == (fileContents = [NSData dataWithContentsOfFile:self.path]))
{
//bail
goto bail;
}
//sha1 it
CC_SHA256(fileContents.bytes, (unsigned int)fileContents.length, digestSHA256);
//now init
self.sha256 = [NSMutableString string];
//convert to NSString
// iterate over each byte in computed digest and format
for(NSUInteger index=0; index < CC_SHA256_DIGEST_LENGTH; index++)
{
//format/append
[self.sha256 appendFormat:@"%02lX", (unsigned long)digestSHA256[index]];
}
bail:
return;
}
@@ -297,44 +288,41 @@ bail:
if(nil == self.signingInfo)
{
//generate
[self generateSigningInfo:kSecCSDefaultFlags entitlements:NO];
[self generateSigningInfo:kSecCSDefaultFlags];
}
//validly signed binary?
// use its signing identifier
if( (noErr == [self.signingInfo[KEY_SIGNATURE_STATUS] intValue]) &&
(0 != [self.signingInfo[KEY_SIGNING_AUTHORITIES] count]) &&
(0 != [self.signingInfo[KEY_SIGNATURE_AUTHORITIES] count]) &&
(nil != self.signingInfo[KEY_SIGNATURE_IDENTIFIER]) )
{
//use signing id
self.identifier = self.signingInfo[KEY_SIGNATURE_IDENTIFIER];
self.identifier = self.signingInfo[KEY_SIGNATURE_IDENTIFIER];
}
//not validly signed or unsigned
// generate sha256 hash for identifier
else
{
//hash
self.identifier = PI_hashFile(self.path);
//generate hash?
if(0 != self.sha256.length)
{
//hash
[self generateHash];
}
//use hash
self.identifier = self.sha256;
}
return;
}
//generate entitlements
// note: can also call 'generateSigningInfo' w/ 'entitlements:YES'
-(void)generateEntitlements
{
//call into helper function
self.entitlements = extractEntitlements(self.path);
return;
}
//for pretty printing
-(NSString *)description
{
//pretty print
return [NSString stringWithFormat: @"name: %@\npath: %@\nattributes: %@\nsigning info: %@ (isApple: %d / isAppStore: %d)", self.name, self.path, self.attributes, self.signingInfo, self.isApple, self.isAppStore];
return [NSString stringWithFormat: @"name: %@\npath: %@\nattributes: %@\nsigning info: %@", self.name, self.path, self.attributes, self.signingInfo];
}
@end
+81 -55
View File
@@ -7,6 +7,7 @@
// License: Creative Commons Attribution-NonCommercial 4.0 International License
//
#import "Signing.h"
#import "procInfo.h"
#import "Utilities.h"
@@ -19,6 +20,7 @@
@synthesize ancestors;
@synthesize arguments;
@synthesize timestamp;
@synthesize signingInfo;
//init
-(id)init
@@ -211,70 +213,73 @@ bail:
//clear out buffer
bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE);
//first attempt to get path via 'proc_pidpath()'
//1st:
// attempt to get path via 'proc_pidpath()'
status = proc_pidpath(self.pid, pathBuffer, sizeof(pathBuffer));
if( (0 != status) &&
(0 != strlen(pathBuffer)) )
{
//init path
self.path = [NSString stringWithUTF8String:pathBuffer];
//all set
goto bail;
}
//otherwise
//2nd:
// try via process's args ('KERN_PROCARGS2')
else
//init mib
// want system's size for max args
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
//set size
size = sizeof(systemMaxArgs);
//get system's size for max args
if(-1 == sysctl(mib, 2, &systemMaxArgs, &size, NULL, 0))
{
//init mib
// want system's size for max args
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
//set size
size = sizeof(systemMaxArgs);
//get system's size for max args
if(-1 == sysctl(mib, 2, &systemMaxArgs, &size, NULL, 0))
{
//bail
goto bail;
}
//alloc space for args
processArgs = calloc(systemMaxArgs, sizeof(char));
if(NULL == processArgs)
{
//bail
goto bail;
}
//init mib
// want process args
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = pid;
//set size
size = (size_t)systemMaxArgs;
//get process's args
if(-1 == sysctl(mib, 3, processArgs, &size, NULL, 0))
{
//bail
goto bail;
}
//sanity check
// ensure buffer is somewhat sane
if(size <= sizeof(int))
{
//bail
goto bail;
}
//extract process name
// follows # of args (int) and is NULL-terminated
self.path = [NSString stringWithUTF8String:processArgs + sizeof(int)];
//bail
goto bail;
}
//alloc space for args
processArgs = calloc(systemMaxArgs, sizeof(char));
if(NULL == processArgs)
{
//bail
goto bail;
}
//init mib
// want process args
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = pid;
//set size
size = (size_t)systemMaxArgs;
//get process's args
if(-1 == sysctl(mib, 3, processArgs, &size, NULL, 0))
{
//bail
goto bail;
}
//sanity check
// ensure buffer is somewhat sane
if(size <= sizeof(int))
{
//bail
goto bail;
}
//extract process name
// follows # of args (int) and is NULL-terminated
self.path = [NSString stringWithUTF8String:processArgs + sizeof(int)];
bail:
//free process args
@@ -488,11 +493,33 @@ bail:
return;
}
//generate signing info
// also classifies if Apple/from App Store/etc.
-(void)generateSigningInfo:(SecCSFlags)flags
{
//extract signing info dynamically
self.signingInfo = extractSigningInfo(self.pid, nil, flags);
//failed?
// try extract signing info statically
if(nil == self.signingInfo)
{
//add 'all archs' / 'nested'
// cuz don't know which is/was running
flags |= kSecCSCheckAllArchitectures | kSecCSCheckNestedCode | kSecCSDoNotValidateResources;
//extract signing info statically
self.signingInfo = extractSigningInfo(0, self.binary.path, flags);
}
return;
}
//for pretty printing
-(NSString *)description
{
//pretty print
return [NSString stringWithFormat: @"pid: %d\npath: %@\nuser: %d\nargs: %@\nancestors: %@\nbinary:\n%@", self.pid, self.path, self.uid, self.arguments, self.ancestors, self.binary];
return [NSString stringWithFormat: @"pid: %d\npath: %@\nuser: %d\nargs: %@\nancestors: %@\n signing info: %@\n binary:\n%@", self.pid, self.path, self.uid, self.arguments, self.ancestors, self.signingInfo, self.binary];
}
//class method to get parent of arbitrary process
@@ -533,5 +560,4 @@ bail:
return parentID;
}
@end
+15 -14
View File
@@ -45,7 +45,7 @@
//init
// just check OS version
-(id _Nullable )init
-(id _Nullable)init
{
//super
self = [super init];
@@ -73,7 +73,7 @@ bail:
//init w/ flag
// flag dictates if CPU-intensive logic (code signing, etc) should be preformed
-(id _Nullable )init:(BOOL)goEasy;
-(id _Nullable)init:(BOOL)goEasy;
{
//init
// calls 'super' too
@@ -486,7 +486,7 @@ bail:
//handle process starts
else
{
//try get process path
//also try get process path
// this is the most 'trusted way' (since exec_args can change)
[process pathFromPid];
@@ -661,11 +661,14 @@ bail:
return;
}
//handle new process
// create Binary obj/enum/process ancestors, etc
-(void)handleProcessStart:(Process*)process
{
//default cs flags
// note: since this is dynamic check, we don't need to check all architectures, or skip resources, etc...
SecCSFlags flags = kSecCSDefaultFlags;
//sanity check
// should only occur for fork() events, which normally get superceeded by an exec(), etc
if( (-1 == process.pid) ||
@@ -705,7 +708,8 @@ bail:
if(YES != self.goEasy)
{
//generate signing info
[process.binary generateSigningInfo:kSecCSCheckAllArchitectures entitlements:NO];
// first will try dynamic, falling back to static
[process generateSigningInfo:flags];
//set icon
[process.binary getIcon];
@@ -732,6 +736,10 @@ bail:
//return array of running processes
-(NSMutableArray*)currentProcesses
{
//default cs flags
// note: since this is dynamic check, we don't need to check all architectures, or skip resources, etc...
SecCSFlags flags = kSecCSDefaultFlags;
//current process
Process* currentProcess = nil;
@@ -745,15 +753,8 @@ bail:
// init process object w/ pid/path, etc
for(NSNumber* pid in PI_enumerateProcesses())
{
//skip 'blank' pids
if(0 == pid.unsignedShortValue)
{
//skip
continue;
}
//create process obj
currentProcess = [[Process alloc] init:pid.unsignedShortValue];
currentProcess = [[Process alloc] init:pid.intValue];
if(nil == currentProcess)
{
//skip
@@ -761,7 +762,7 @@ bail:
}
//generate signing info
[currentProcess.binary generateSigningInfo:kSecCSCheckAllArchitectures entitlements:NO];
[currentProcess generateSigningInfo:flags];
//add
[processes addObject:currentProcess];
+10 -17
View File
@@ -15,25 +15,18 @@
/* FUNCTIONS */
//get the signing info of a file
NSMutableDictionary* extractSigningInfo(NSString* path, SecCSFlags flags, BOOL entitlements);
//get the signing info of a item
// pid specified: extract dynamic code signing info
// path specified: generate static code signing info
NSMutableDictionary* extractSigningInfo(pid_t pid, NSString* path, SecCSFlags flags);
//determine if a file is signed by Apple proper
BOOL isApple(NSString* path, SecCSFlags flags);
//determine who signed item
NSNumber* extractSigner(SecStaticCodeRef code, SecCSFlags flags, BOOL isDynamic);
//determine if file is signed with Apple Dev ID/cert
BOOL isSignedDevID(NSString* path, SecCSFlags flags);
//validate a requirement
OSStatus validateRequirement(SecStaticCodeRef code, SecRequirementRef requirement, SecCSFlags flags, BOOL isDynamic);
//determine if a file is from the app store
// gotta be signed w/ Apple Dev ID & have valid app receipt
BOOL fromAppStore(NSString* path);
//get GUID (really just computer's MAC address)
// from Apple's 'Get the GUID in OS X' (see: 'Validating Receipts Locally')
NSData* getGUID(void);
//extact entitlements
// note: execs apple's 'codesign' binary
NSDictionary* extractEntitlements(NSString* path);
//extract (names) of signing auths
NSMutableArray* extractSigningAuths(NSDictionary* signingDetails);
#endif
+248 -682
View File
@@ -11,265 +11,146 @@
#import "Signing.h"
#import "procInfo.h"
#import "Utilities.h"
#import "AppReceipt.h"
#import <mach-o/fat.h>
#import <mach-o/arch.h>
#import <mach-o/swap.h>
#import <sys/sysctl.h>
#import <Security/Security.h>
#import <CommonCrypto/CommonDigest.h>
#import <SystemConfiguration/SystemConfiguration.h>
//determine the offset (if any)
// of the 'best' architecture in a (fat) binary
uint32_t bestArchOffset(NSString* path)
{
//offset of best architecture
uint32_t offset = 0;
//pool
@autoreleasepool
{
//binary
NSMutableData* binary = nil;
//le bytez
const void* binaryBytes = NULL;
//fat header
struct fat_header* fatHeader = NULL;
//number of fat architectures
uint32_t fatArchitectureCount = 0;
//fat architectures
void *fatArchitectures = NULL;
//local architecture
const NXArchInfo *localArchitecture = NULL;
//best matching architecture
struct fat_arch *bestArchitecture = NULL;
//load binary into memory
binary = [NSMutableData dataWithContentsOfFile:path];
if(binary.length < sizeof(struct fat_header))
{
//bail
goto bail;
}
//grab bytes
binaryBytes = binary.bytes;
//not universal (fat)
if( (FAT_MAGIC != *(const uint32_t *)binaryBytes) &&
(FAT_CIGAM != *(const uint32_t *)binaryBytes) )
{
//bail
goto bail;
}
//binary is fat
// init pointer to fat header
fatHeader = (struct fat_header*)binaryBytes;
//swap size?
if(fatHeader->magic == OSSwapHostToBigInt32(FAT_MAGIC))
{
//swap
fatArchitectureCount = OSSwapBigToHostInt32(fatHeader->nfat_arch);
}
//sanity check
if(binary.length <= sizeof(struct fat_header) + fatArchitectureCount * sizeof(struct fat_arch))
{
//bail
goto bail;
}
//init pointer to fat architectures
fatArchitectures = (char*)binaryBytes + sizeof(struct fat_header);
//get local architecture
localArchitecture = NXGetLocalArchInfo();
//swap fat architectures?
if(fatHeader->magic == OSSwapHostToBigInt32(FAT_MAGIC))
{
//swap
swap_fat_arch(fatArchitectures, fatArchitectureCount, localArchitecture->byteorder);
}
//find best architecture
bestArchitecture = NXFindBestFatArch(localArchitecture->cputype, localArchitecture->cpusubtype, fatArchitectures, fatArchitectureCount);
if(NULL == bestArchitecture)
{
//bail
goto bail;
}
//init offset
offset = bestArchitecture->offset;
bail:
;
}//autorelease
return offset;
}
//get the signing info of a item
NSMutableDictionary* extractSigningInfo(NSString* path, SecCSFlags flags, BOOL entitlements)
// pid specified: extract dynamic code signing info
// path specified: generate static code signing info
NSMutableDictionary* extractSigningInfo(pid_t pid, NSString* path, SecCSFlags flags)
{
//info dictionary
NSMutableDictionary* signingInfo = nil;
//offset of best architecture
// for universal/fat binary, need to check correct arch
uint32_t offset = 0;
//status
OSStatus status = !errSecSuccess;
//code
//static code ref
SecStaticCodeRef staticCode = NULL;
//status
OSStatus status = -1;
//dynamic code ref
SecCodeRef dynamicCode = NULL;
//signing information
//signing details
CFDictionaryRef signingDetails = NULL;
//cert chain
NSArray* certificateChain = nil;
//signing authorities
NSMutableArray* signingAuths = nil;
//index
NSUInteger index = 0;
//cert
SecCertificateRef certificate = NULL;
//common name on chert
CFStringRef commonName = NULL;
//init signing status
signingInfo = [NSMutableDictionary dictionary];
//sanity check
//dynamic code checks
// no path, dynamic check via pid
if(nil == path)
{
//set err
signingInfo[KEY_SIGNATURE_STATUS] = [NSNumber numberWithInt:errSecCSObjectRequired];
//bail
goto bail;
}
//get offset of 'best' architecute
// this is what loader will run, and thus, what we should validate!
offset = bestArchOffset(path);
//create static code
status = SecStaticCodeCreateWithPathAndAttributes((__bridge CFURLRef)([NSURL fileURLWithPath:path]), kSecCSDefaultFlags, (__bridge CFDictionaryRef)@{(__bridge NSString *)kSecCodeAttributeUniversalFileOffset : [NSNumber numberWithUnsignedInt:offset]}, &staticCode);
//save signature status
signingInfo[KEY_SIGNATURE_STATUS] = [NSNumber numberWithInt:status];
if(noErr != status)
{
//bail
goto bail;
}
//check signature
status = SecStaticCodeCheckValidity(staticCode, flags, NULL);
//(re)save signature status
signingInfo[KEY_SIGNATURE_STATUS] = [NSNumber numberWithInt:status];
if(noErr != status)
{
//bail
goto bail;
}
//grab signing info
status = SecCodeCopySigningInformation(staticCode, kSecCSSigningInformation, &signingDetails);
if(noErr != status)
{
//bail
goto bail;
}
//grab signing ID
signingInfo[KEY_SIGNATURE_IDENTIFIER] = [(__bridge NSDictionary*)signingDetails objectForKey:(__bridge NSString*)kSecCodeInfoIdentifier];
//determine if binary is signed by Apple
signingInfo[KEY_SIGNING_IS_APPLE] = [NSNumber numberWithBool:isApple(path, flags)];
//not apple proper
// is signed with Apple Dev ID?
if(YES != [signingInfo[KEY_SIGNING_IS_APPLE] boolValue])
{
//determine if binary is Apple Dev ID
signingInfo[KEY_SIGNING_IS_APPLE_DEV_ID] = [NSNumber numberWithBool:isSignedDevID(path, flags)];
//if dev id
// from app store?
if(YES == [signingInfo[KEY_SIGNING_IS_APPLE_DEV_ID] boolValue])
//generate dynamic code ref via pid
if(errSecSuccess != SecCodeCopyGuestWithAttributes(NULL, (__bridge CFDictionaryRef _Nullable)(@{(__bridge NSString *)kSecGuestAttributePid : [NSNumber numberWithInt:pid]}), kSecCSDefaultFlags, &dynamicCode))
{
//from app store?
signingInfo[KEY_SIGNING_IS_APP_STORE] = [NSNumber numberWithBool:fromAppStore(path)];
}
}
//init array for certificate names
signingInfo[KEY_SIGNING_AUTHORITIES] = [NSMutableArray array];
//get cert chain
certificateChain = [(__bridge NSDictionary*)signingDetails objectForKey:(__bridge NSString*)kSecCodeInfoCertificates];
//get name of all certs
// add each to list
for(index = 0; index < certificateChain.count; index++)
{
//reset
commonName = NULL;
//extract cert
certificate = (__bridge SecCertificateRef)([certificateChain objectAtIndex:index]);
//get common name
status = SecCertificateCopyCommonName(certificate, &commonName);
//skip ones that error out
if( (noErr != status) ||
(NULL == commonName))
{
//release
if(NULL != commonName)
{
//release
CFRelease(commonName);
}
//skip
continue;
//bail
goto bail;
}
//now, init signing status
signingInfo = [NSMutableDictionary dictionary];
//validate code
status = SecCodeCheckValidity(dynamicCode, flags, NULL);
//save result
signingInfo[KEY_SIGNATURE_STATUS] = [NSNumber numberWithInt:status];
//sanity check
// bail on error
if(errSecSuccess != status)
{
//bail
goto bail;
}
//extract signing info
status = SecCodeCopySigningInformation(dynamicCode, kSecCSSigningInformation, &signingDetails);
//save result
signingInfo[KEY_SIGNATURE_STATUS] = [NSNumber numberWithInt:status];
//sanity check
// bail on error
if(errSecSuccess != status)
{
//bail
goto bail;
}
//determine signer
// apple, app store, dev id, adhoc, etc...
signingInfo[KEY_SIGNATURE_SIGNER] = extractSigner(dynamicCode, flags, YES);
}
//static code checks
else
{
//create static code ref via path
if(errSecSuccess != SecStaticCodeCreateWithPath((__bridge CFURLRef)([NSURL fileURLWithPath:path]), kSecCSDefaultFlags, &staticCode))
{
//bail
goto bail;
}
//now, init signing status
signingInfo = [NSMutableDictionary dictionary];
//check signature
status = SecStaticCodeCheckValidity(staticCode, flags, NULL);
//save result
signingInfo[KEY_SIGNATURE_STATUS] = [NSNumber numberWithInt:status];
//sanity check
// bail on error
if(errSecSuccess != status)
{
//bail
goto bail;
}
//extract signing info
status = SecCodeCopySigningInformation(staticCode, kSecCSSigningInformation, &signingDetails);
//save result
signingInfo[KEY_SIGNATURE_STATUS] = [NSNumber numberWithInt:status];
//sanity check
// bail on error
if(errSecSuccess != status)
{
//bail
goto bail;
}
//determine signer
// apple, app store, dev id, adhoc, etc...
signingInfo[KEY_SIGNATURE_SIGNER] = extractSigner(staticCode, flags, NO);
}
//extract code signing id
if(nil != [(__bridge NSDictionary*)signingDetails objectForKey:(__bridge NSString*)kSecCodeInfoIdentifier])
{
//extract/save
signingInfo[KEY_SIGNATURE_IDENTIFIER] = [(__bridge NSDictionary*)signingDetails objectForKey:(__bridge NSString*)kSecCodeInfoIdentifier];
}
//extract entitlements
if(nil != [(__bridge NSDictionary*)signingDetails objectForKey:(__bridge NSString*)kSecCodeInfoEntitlementsDict])
{
//extract/save
signingInfo[KEY_SIGNATURE_ENTITLEMENTS] = [(__bridge NSDictionary*)signingDetails objectForKey:(__bridge NSString*)kSecCodeInfoEntitlementsDict];
}
//extract signing authorities
signingAuths = extractSigningAuths((__bridge NSDictionary *)(signingDetails));
if(0 != signingAuths.count)
{
//save
[signingInfo[KEY_SIGNING_AUTHORITIES] addObject:(__bridge NSString*)commonName];
//release name
CFRelease(commonName);
}
//add entitlements?
if(YES == entitlements)
{
//extract entitlements via Apple's 'codesign'
signingInfo[KEY_SIGNING_ENTITLEMENTS] = extractEntitlements(path);
signingInfo[KEY_SIGNATURE_AUTHORITIES] = signingAuths;
}
bail:
@@ -284,6 +165,16 @@ bail:
signingDetails = NULL;
}
//free dynamic code
if(NULL != dynamicCode)
{
//free
CFRelease(dynamicCode);
//unset
dynamicCode = NULL;
}
//free static code
if(NULL != staticCode)
{
@@ -297,472 +188,147 @@ bail:
return signingInfo;
}
//determine if a file is signed by Apple proper
BOOL isApple(NSString* path, SecCSFlags flags)
//determine who signed item
NSNumber* extractSigner(SecStaticCodeRef code, SecCSFlags flags, BOOL isDynamic)
{
//flag
BOOL isApple = NO;
//result
NSNumber* signer = nil;
//code
SecStaticCodeRef staticCode = NULL;
//"anchor apple"
static SecRequirementRef isApple = nil;
//signing reqs
SecRequirementRef requirementRef = NULL;
//"anchor apple generic"
static SecRequirementRef isDevID = nil;
//status
OSStatus status = -1;
//"anchor apple generic and certificate leaf [subject.CN] = \"Apple Mac OS Application Signing\""
static SecRequirementRef isAppStore = nil;
//create static code
status = SecStaticCodeCreateWithPath((__bridge CFURLRef)([NSURL fileURLWithPath:path]), kSecCSDefaultFlags, &staticCode);
if(noErr != status)
{
//bail
goto bail;
}
//create req string w/ 'anchor apple'
// (3rd party: 'anchor apple generic')
status = SecRequirementCreateWithString(CFSTR("anchor apple"), kSecCSDefaultFlags, &requirementRef);
if( (noErr != status) ||
(requirementRef == NULL) )
{
//bail
goto bail;
}
//check if file is signed by apple by checking if it conforms to req string
// note: ignore 'errSecCSBadResource' as lots of signed apple files return this issue :/
status = SecStaticCodeCheckValidity(staticCode, flags, requirementRef);
if( (noErr != status) &&
(errSecCSBadResource != status) )
{
//bail
// just means isn't signed by apple
goto bail;
}
//ok, happy (SecStaticCodeCheckValidity() didn't fail)
// file is signed by Apple
isApple = YES;
bail:
//free req reference
if(NULL != requirementRef)
{
//free
CFRelease(requirementRef);
//unset
requirementRef = NULL;
}
//free static code
if(NULL != staticCode)
{
//free
CFRelease(staticCode);
//unset
staticCode = NULL;
}
return isApple;
}
//verify the receipt
// check bundle ID, app version, and receipt's hash
BOOL verifyReceipt(NSBundle* appBundle, AppReceipt* receipt)
{
//flag
BOOL verified = NO;
//guid
NSData* guid = nil;
//hash data
NSMutableData *digestData = nil;
//hash buffer
unsigned char digestBuffer[CC_SHA1_DIGEST_LENGTH] = {0};
//check guid
guid = getGUID();
if(nil == guid)
{
//bail
goto bail;
}
//create data obj
digestData = [NSMutableData data];
//add guid to data obj
[digestData appendData:guid];
//add receipt's 'opaque value' to data obj
[digestData appendData:receipt.opaqueValue];
//add receipt's bundle id data to data obj
[digestData appendData:receipt.bundleIdentifierData];
//CHECK 1:
// app's bundle ID should match receipt's bundle ID
if(YES != [receipt.bundleIdentifier isEqualToString:appBundle.bundleIdentifier])
{
//bail
goto bail;
}
//CHECK 2:
// app's version should match receipt's version
if(YES != [receipt.appVersion isEqualToString:appBundle.infoDictionary[@"CFBundleShortVersionString"]])
{
//bail
goto bail;
}
//CHECK 3:
// verify receipt's hash (UUID)
//init SHA 1 hash
CC_SHA1(digestData.bytes, (CC_LONG)digestData.length, digestBuffer);
//check for hash match
if(0 != memcmp(digestBuffer, receipt.receiptHash.bytes, CC_SHA1_DIGEST_LENGTH))
{
//hash check failed
goto bail;
}
//happy
verified = YES;
bail:
return verified;
}
//get GUID (really just computer's MAC address)
// from Apple's 'Get the GUID in OS X' (see: 'Validating Receipts Locally')
NSData* getGUID()
{
//status var
__block kern_return_t kernResult = -1;
//master port
__block mach_port_t masterPort = 0;
//matching dictionar
__block CFMutableDictionaryRef matchingDict = NULL;
//iterator
__block io_iterator_t iterator = 0;
//service
__block io_object_t service = 0;
//registry property
__block CFDataRef registryProperty = NULL;
//guid (MAC addr)
static NSData* guid = nil;
//once token
//token
static dispatch_once_t onceToken = 0;
//only init guid once
dispatch_once(&onceToken,
^{
//get master port
kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort);
if(KERN_SUCCESS != kernResult)
{
//bail
goto bail;
}
//get matching dictionary for 'en0'
matchingDict = IOBSDNameMatching(masterPort, 0, "en0");
if(NULL == matchingDict)
{
//bail
goto bail;
}
//get matching services
kernResult = IOServiceGetMatchingServices(masterPort, matchingDict, &iterator);
if(KERN_SUCCESS != kernResult)
{
//bail
goto bail;
}
//iterate over services, looking for 'IOMACAddress'
while((service = IOIteratorNext(iterator)) != 0)
{
//parent
io_object_t parentService = 0;
//get parent
kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parentService);
if(KERN_SUCCESS == kernResult)
{
//release prev
if(NULL != registryProperty)
{
//release
CFRelease(registryProperty);
}
//get registry property for 'IOMACAddress'
registryProperty = (CFDataRef) IORegistryEntryCreateCFProperty(parentService, CFSTR("IOMACAddress"), kCFAllocatorDefault, 0);
//release parent
IOObjectRelease(parentService);
}
//release service
IOObjectRelease(service);
}
//release iterator
IOObjectRelease(iterator);
//convert guid to NSData*
// also release registry property
if(NULL != registryProperty)
{
//convert
guid = [NSData dataWithData:(__bridge NSData *)registryProperty];
//release
CFRelease(registryProperty);
}
bail:
;
});//only once
return guid;
}
//determine if file is signed with Apple Dev ID/cert
BOOL isSignedDevID(NSString* path, SecCSFlags flags)
{
//flag
BOOL signedOK = NO;
//code
SecStaticCodeRef staticCode = NULL;
//signing reqs
SecRequirementRef requirementRef = NULL;
//status
OSStatus status = -1;
//create static code
status = SecStaticCodeCreateWithPath((__bridge CFURLRef)([NSURL fileURLWithPath:path]), kSecCSDefaultFlags, &staticCode);
if(noErr != status)
{
//bail
goto bail;
}
//create req string w/ 'anchor apple generic'
status = SecRequirementCreateWithString(CFSTR("anchor apple generic"), kSecCSDefaultFlags, &requirementRef);
if( (noErr != status) ||
(requirementRef == NULL) )
{
//bail
goto bail;
}
//check if file is signed w/ apple dev id by checking if it conforms to req string
status = SecStaticCodeCheckValidity(staticCode, flags, requirementRef);
if(noErr != status)
{
//bail
// just means app isn't signed by apple dev id
goto bail;
}
//ok, happy
// file is signed by Apple Dev ID
signedOK = YES;
bail:
//free req reference
if(NULL != requirementRef)
{
//free
CFRelease(requirementRef);
//only once
// init requirements
dispatch_once(&onceToken, ^{
//unset
requirementRef = NULL;
}
//free static code
if(NULL != staticCode)
{
//free
CFRelease(staticCode);
//init apple signing requirement
SecRequirementCreateWithString(CFSTR("anchor apple"), kSecCSDefaultFlags, &isApple);
//unset
staticCode = NULL;
}
//init dev id signing requirement
SecRequirementCreateWithString(CFSTR("anchor apple generic"), kSecCSDefaultFlags, &isDevID);
//init app store signing requirement
SecRequirementCreateWithString(CFSTR("anchor apple generic and certificate leaf [subject.CN] = \"Apple Mac OS Application Signing\""), kSecCSDefaultFlags, &isAppStore);
});
return signedOK;
}
//determine if a file is from the app store
// gotta be signed w/ Apple Dev ID & have valid app receipt
// note: here, assume this function is only called on Apps signed with Apple Dev ID!
BOOL fromAppStore(NSString* path)
{
//flag
BOOL appStoreApp = NO;
//app receipt
AppReceipt* appReceipt = nil;
//path to app bundle
// just have binary
NSBundle* appBundle = nil;
//if it's an app
// can directly load app bundle
appBundle = [NSBundle bundleWithPath:path];
if(nil == appBundle)
//check 1: "is apple" (proper)
if(errSecSuccess == validateRequirement(code, isApple, flags, isDynamic))
{
//find app bundle from binary
// likely not an application if this fails
appBundle = PI_findAppBundle(path);
if(nil == appBundle)
{
//bail
goto bail;
}
//set signer to apple
signer = [NSNumber numberWithInt:Apple];
}
//bail if it doesn't have an receipt
// done here, since checking signature is expensive!
if( (nil == appBundle.appStoreReceiptURL) ||
(YES != [[NSFileManager defaultManager] fileExistsAtPath:appBundle.appStoreReceiptURL.path]) )
//check 2: "is app store"
// note: this is more specific than dev id, so do it first
else if(errSecSuccess == validateRequirement(code, isAppStore, flags, isDynamic))
{
//bail
goto bail;
//set signer to app store
signer = [NSNumber numberWithInt:AppStore];
}
//init
// will parse/decode, etc
appReceipt = [[AppReceipt alloc] init:appBundle];
if(nil == appReceipt)
//check 3: "is dev id"
else if(errSecSuccess == validateRequirement(code, isDevID, flags, isDynamic))
{
//bail
goto bail;
//set signer to dev id
signer = [NSNumber numberWithInt:DevID];
}
//verify
if(YES != verifyReceipt(appBundle, appReceipt))
{
//bail
goto bail;
}
//happy
// app is signed w/ dev ID & its receipt is solid
appStoreApp = YES;
bail:
return appStoreApp;
}
//extact entitlements
// note: execs apple's 'codesign' binary
NSDictionary* extractEntitlements(NSString* path)
{
//entitlements
NSDictionary* entitlements = nil;
//results
NSMutableDictionary* results = nil;
//entitlements at bytes
unsigned char* entitlementBytes = nil;
//entitlements as data
NSData* entitlementsData = nil;
//entitlements as xml
NSString* entitlementsXML = nil;
//if path is '/usr/bin/codesign'
// this isn't entitled, and to avoid recursive calling, just bail
if(YES == [path isEqualToString:CODE_SIGN])
{
//bail
goto bail;
}
//exec 'codesign'
results = PI_execTask(CODE_SIGN, @[@"-d", @"--entitlements", @"-", path], YES, YES);
if(noErr != [results[EXIT_CODE] intValue])
{
//bail
goto bail;
}
//not entitled?
// could just check for nil, but use offset below
if([results[STDOUT] length] < 0x10)
{
//bail
goto bail;
}
//grab bytes
entitlementBytes = (unsigned char*)[results[STDOUT] bytes];
//codesign has a bug where it returns some (encoding?) bytes first
// check for that here, and if found, start string conversion at offset 0x8
if(0xFA == entitlementBytes[0])
{
//convert to string
entitlementsXML = [[NSString alloc] initWithData:[results[STDOUT] subdataWithRange:NSMakeRange(0x8, [results[STDOUT] length] - 0x8)] encoding:NSUTF8StringEncoding];
}
//other just convert as is
//otherwise
// has to be adhoc?
else
{
//convert to string
entitlementsXML = [[NSString alloc] initWithData:results[STDOUT] encoding:NSUTF8StringEncoding];
//set signer to ad hoc
signer = [NSNumber numberWithInt:AdHoc];
}
//sanity check
// make sure conversion to string ok
if(0 == [entitlementsXML length])
{
//bail
goto bail;
}
//convert to data
entitlementsData = [entitlementsXML dataUsingEncoding:NSUTF8StringEncoding];
if(nil == entitlementsData)
{
//bail
goto bail;
}
//convert to dictionary
entitlements = [NSPropertyListSerialization propertyListWithData:entitlementsData options:NSPropertyListImmutable format:nil error:nil];
bail:
return entitlements;
return signer;
}
//validate a requirement
OSStatus validateRequirement(SecStaticCodeRef code, SecRequirementRef requirement, SecCSFlags flags, BOOL isDynamic)
{
//result
OSStatus result = -1;
//dynamic check?
if(YES == isDynamic)
{
//validate dynamically
result = SecCodeCheckValidity((SecCodeRef)code, flags, requirement);
}
//static check
else
{
//validate statically
result = SecStaticCodeCheckValidity(code, flags, requirement);
}
return result;
}
//extract (names) of signing auths
NSMutableArray* extractSigningAuths(NSDictionary* signingDetails)
{
//signing auths
NSMutableArray* authorities = nil;
//cert chain
NSArray* certificateChain = nil;
//index
NSUInteger index = 0;
//cert
SecCertificateRef certificate = NULL;
//common name on chert
CFStringRef commonName = NULL;
//init array for certificate names
authorities = [NSMutableArray array];
//get cert chain
certificateChain = [signingDetails objectForKey:(__bridge NSString*)kSecCodeInfoCertificates];
//get name of all certs
// add each to list
for(index = 0; index < certificateChain.count; index++)
{
//reset
commonName = NULL;
//extract cert
certificate = (__bridge SecCertificateRef)([certificateChain objectAtIndex:index]);
//get common name
if(errSecSuccess != SecCertificateCopyCommonName(certificate, &commonName))
{
//release
if(NULL != commonName)
{
//release
CFRelease(commonName);
}
//next
continue;
}
//save
[authorities addObject:(__bridge NSString*)commonName];
//release name
CFRelease(commonName);
}
return authorities;
}
-7
View File
@@ -17,7 +17,6 @@
NSBundle* PI_findAppBundle(NSString* binaryPath);
//check if current OS version is supported
// for now, just...?
BOOL PI_isSupportedOS(void);
//get OS version
@@ -30,14 +29,8 @@ NSMutableArray* PI_enumerateProcesses(void);
// find its executable
NSString* PI_findAppBinary(NSString* appPath);
//sha256 a file
NSString* PI_hashFile(NSString* filePath);
//given a 'short' path or process name
// find the full path by scanning $PATH
NSString* PI_which(NSString* processName);
//exec a process with args
NSMutableDictionary* PI_execTask(NSString* binaryPath, NSArray* arguments, BOOL shouldWait, BOOL grabOutput);
#endif
+8 -199
View File
@@ -9,7 +9,6 @@
#import "Consts.h"
#import "Utilities.h"
#import "AppReceipt.h"
#import <libproc.h>
#import <sys/sysctl.h>
@@ -123,9 +122,6 @@ bail:
//enumerate all running processes
NSMutableArray* PI_enumerateProcesses()
{
//status
int status = -1;
//# of procs
int numberOfProcesses = 0;
@@ -139,7 +135,12 @@ NSMutableArray* PI_enumerateProcesses()
processes = [NSMutableArray array];
//get # of procs
numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0);
numberOfProcesses = proc_listallpids(NULL, 0);
if(-1 == numberOfProcesses)
{
//bail
goto bail;
}
//alloc buffer for pids
pids = calloc(numberOfProcesses, sizeof(pid_t));
@@ -150,8 +151,8 @@ NSMutableArray* PI_enumerateProcesses()
}
//get list of pids
status = proc_listpids(PROC_ALL_PIDS, 0, pids, numberOfProcesses * sizeof(pid_t));
if(status < 0)
numberOfProcesses = proc_listallpids(pids, numberOfProcesses*sizeof(pid_t));
if(-1 == numberOfProcesses)
{
//bail
goto bail;
@@ -229,47 +230,6 @@ NSBundle* PI_findAppBundle(NSString* binaryPath)
return appBundle;
}
//hash a file
// algorithm: sha256
NSString* PI_hashFile(NSString* filePath)
{
//file's contents
NSData* fileContents = nil;
//hash digest
uint8_t digestSHA256[CC_SHA256_DIGEST_LENGTH] = {0};
//hash as string
NSMutableString* sha256 = nil;
//index var
NSUInteger index = 0;
//init
sha256 = [NSMutableString string];
//load file
if(nil == (fileContents = [NSData dataWithContentsOfFile:filePath]))
{
//bail
goto bail;
}
//sha1 it
CC_SHA256(fileContents.bytes, (unsigned int)fileContents.length, digestSHA256);
//convert to NSString
// iterate over each bytes in computed digest and format
for(index=0; index < CC_SHA256_DIGEST_LENGTH; index++)
{
//format/append
[sha256 appendFormat:@"%02lX", (unsigned long)digestSHA256[index]];
}
bail:
return sha256;
}
//given a 'short' path or process name
// try find the full path by scanning $PATH
@@ -319,154 +279,3 @@ NSString* PI_which(NSString* processName)
return fullPath;
}
//exec a process with args
NSMutableDictionary* PI_execTask(NSString* binaryPath, NSArray* arguments, BOOL shouldWait, BOOL grabOutput)
{
//task
NSTask* task = nil;
//output pipe for stdout
NSPipe* stdOutPipe = nil;
//output pipe for stderr
NSPipe* stdErrPipe = nil;
//read handle for stdout
NSFileHandle* stdOutReadHandle = nil;
//read handle for stderr
NSFileHandle* stdErrReadHandle = nil;
//results dictionary
NSMutableDictionary* results = nil;
//output for stdout
NSMutableData *stdOutData = nil;
//output for stderr
NSMutableData *stdErrData = nil;
//init dictionary for results
results = [NSMutableDictionary dictionary];
//init task
task = [[NSTask alloc] init];
//only setup pipes if wait flag is set
if(YES == grabOutput)
{
//init stdout pipe
stdOutPipe = [NSPipe pipe];
//init stderr pipe
stdErrPipe = [NSPipe pipe];
//init stdout read handle
stdOutReadHandle = [stdOutPipe fileHandleForReading];
//init stderr read handle
stdErrReadHandle = [stdErrPipe fileHandleForReading];
//init stdout output buffer
stdOutData = [NSMutableData data];
//init stderr output buffer
stdErrData = [NSMutableData data];
//set task's stdout
task.standardOutput = stdOutPipe;
//set task's stderr
task.standardError = stdErrPipe;
}
//set task's path
task.launchPath = binaryPath;
//set task's args
if(nil != arguments)
{
//set
task.arguments = arguments;
}
//wrap task launch
@try
{
//launch
[task launch];
}
@catch(NSException *exception)
{
//bail
goto bail;
}
//no need to wait
// can just bail w/ no output
if( (YES != shouldWait) &&
(YES != grabOutput) )
{
//bail
goto bail;
}
//wait
// ...but no output
else if( (YES == shouldWait) &&
(YES != grabOutput) )
{
//wait
[task waitUntilExit];
//add exit code
results[EXIT_CODE] = [NSNumber numberWithInteger:task.terminationStatus];
//bail
goto bail;
}
//grab output?
// even if wait not set, still will wait!
else
{
//read in stdout/stderr
while(YES == [task isRunning])
{
//accumulate stdout
[stdOutData appendData:[stdOutReadHandle readDataToEndOfFile]];
//accumulate stderr
[stdErrData appendData:[stdErrReadHandle readDataToEndOfFile]];
}
//grab any leftover stdout
[stdOutData appendData:[stdOutReadHandle readDataToEndOfFile]];
//grab any leftover stderr
[stdErrData appendData:[stdErrReadHandle readDataToEndOfFile]];
//add stdout
if(0 != stdOutData.length)
{
//add
results[STDOUT] = stdOutData;
}
//add stderr
if(0 != stdErrData.length)
{
//add
results[STDERR] = stdErrData;
}
//add exit code
results[EXIT_CODE] = [NSNumber numberWithInteger:task.terminationStatus];
}
bail:
return results;
}
+27 -31
View File
@@ -19,7 +19,6 @@
@class Binary;
@class Process;
/* DEFINES */
//from audit_kevents.h
@@ -29,30 +28,29 @@
#define EVENT_EXEC 27
#define EVENT_SPAWN 43190
//signers
enum Signer{None, Apple, AppStore, DevID, AdHoc};
//signature status
#define KEY_SIGNATURE_STATUS @"signatureStatus"
//signer
#define KEY_SIGNATURE_SIGNER @"signatureSigner"
//signing auths
#define KEY_SIGNING_AUTHORITIES @"signingAuthorities"
#define KEY_SIGNATURE_AUTHORITIES @"signatureAuthorities"
//code signing id
#define KEY_SIGNATURE_IDENTIFIER @"signingIdentifier"
#define KEY_SIGNATURE_IDENTIFIER @"signatureIdentifier"
//file belongs to apple?
#define KEY_SIGNING_IS_APPLE @"signedByApple"
//file signed with apple dev id
#define KEY_SIGNING_IS_APPLE_DEV_ID @"signedWithDevID"
//from app store
#define KEY_SIGNING_IS_APP_STORE @"fromAppStore"
//entitlements
#define KEY_SIGNATURE_ENTITLEMENTS @"signatureEntitlements"
/* TYPEDEFS */
//block for library
typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
/* OBJECT: PROCESS INFO */
@interface ProcInfo : NSObject
@@ -103,6 +101,9 @@ typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
//ancestors
@property(nonatomic, retain)NSMutableArray* _Nonnull ancestors;
//signing info
@property(nonatomic, retain)NSMutableDictionary* _Nonnull signingInfo;
//Binary object
// has path, hash, etc
@property(nonatomic, retain)Binary* _Nonnull binary;
@@ -116,13 +117,18 @@ typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
// method will then (try) fill out rest of object
-(id _Nullable)init:(pid_t)processID;
//generate signing info
// also classifies if Apple/from App Store/etc.
-(void)generateSigningInfo:(SecCSFlags)flags;
//set process's path
-(void)pathFromPid;
//generate list of ancestors
-(void)enumerateAncestors;
//class method to get parent of arbitrary process
//class method
// get's parent of arbitrary process
+(pid_t)getParentID:(pid_t)child;
@end
@@ -158,22 +164,13 @@ typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
//signing info
@property(nonatomic, retain)NSDictionary* _Nonnull signingInfo;
//entitlements
@property(nonatomic, retain)NSDictionary* _Nonnull entitlements;
//hash
@property(nonatomic, retain)NSString* _Nonnull sha256;
@property(nonatomic, retain)NSMutableString* _Nonnull sha256;
//identifier
// either signing id or sha256 hash
@property(nonatomic, retain)NSString* _Nonnull identifier;
//flag indicating binary belongs to Apple OS
@property BOOL isApple;
//flag indicating binary is from official App Store
@property BOOL isAppStore;
/* METHODS */
//init w/ a path
@@ -187,20 +184,19 @@ typedef void (^ProcessCallbackBlock)(Process* _Nonnull);
// for apps, this will be app's icon, otherwise just a standard system one
-(void)getIcon;
//generate signing info
// also classifies if Apple/from App Store/etc.
-(void)generateSigningInfo:(SecCSFlags)flags entitlements:(BOOL)entitlements;
//generate signing info (statically)
-(void)generateSigningInfo:(SecCSFlags)flags;
//generate entitlements
// note: can also call 'generateSigningInfo' w/ 'entitlements:YES'
-(void)generateEntitlements;
/* the following methods are not invoked automatically
as such, if you code has to manually invoke them if you want this info
*/
//generate hash
// algo: sha256
-(void)generateHash;
//generate id
// eithersigning id, or sha256 hash
// note: will generate signing info if needed
// either signing id, or sha256 hash
-(void)generateIdentifier;
@end