mirror of
https://github.com/objective-see/BlockBlock.git
synced 2026-03-22 07:02:39 +00:00
933 lines
22 KiB
Objective-C
933 lines
22 KiB
Objective-C
//
|
|
// Process.m
|
|
// FileMonitor
|
|
//
|
|
// Created by Patrick Wardle on 9/1/19.
|
|
// Copyright © 2020 Objective-See. All rights reserved.
|
|
//
|
|
|
|
@import OSLog;
|
|
|
|
#import <dlfcn.h>
|
|
#import <libproc.h>
|
|
#import <bsm/libbsm.h>
|
|
#import <sys/sysctl.h>
|
|
|
|
#import "signing.h"
|
|
#import "utilities.h"
|
|
#import "FileMonitor.h"
|
|
|
|
//hash length
|
|
// from: cs_blobs.h
|
|
#define CS_CDHASH_LEN 20
|
|
|
|
/* GLOBALS */
|
|
|
|
//processes cache
|
|
extern NSCache* processesCache;
|
|
|
|
//responsbile pid
|
|
extern pid_t (*getRPID)(pid_t pid);
|
|
|
|
//log handle
|
|
extern os_log_t logHandle;
|
|
|
|
//interpreters
|
|
extern NSMutableSet* interpreters;
|
|
|
|
/* FUNCTIONS */
|
|
|
|
//helper function
|
|
// get parent of arbitrary process
|
|
pid_t getParentID(pid_t child);
|
|
|
|
@implementation Process
|
|
|
|
@synthesize pid;
|
|
@synthesize exit;
|
|
@synthesize path;
|
|
@synthesize ppid;
|
|
@synthesize event;
|
|
@synthesize script;
|
|
@synthesize ancestors;
|
|
@synthesize arguments;
|
|
@synthesize timestamp;
|
|
@synthesize auditToken;
|
|
@synthesize signingInfo;
|
|
@synthesize architecture;
|
|
|
|
//init
|
|
// flag controls code signing options
|
|
-(id)init:(es_message_t*)message csOption:(NSUInteger)csOption
|
|
{
|
|
//string value
|
|
// used for various conversions
|
|
NSString* string = nil;
|
|
|
|
//process from msg
|
|
es_process_t* process = NULL;
|
|
|
|
//inode
|
|
NSNumber* inode = 0;
|
|
|
|
//cached process
|
|
Process* cachedProcess = nil;
|
|
|
|
//init super
|
|
self = [super init];
|
|
if(nil != self)
|
|
{
|
|
//alloc array for args
|
|
self.arguments = [NSMutableArray array];
|
|
|
|
//alloc array for parents
|
|
self.ancestors = [NSMutableArray array];
|
|
|
|
//alloc dictionary for signing info
|
|
self.signingInfo = [NSMutableDictionary dictionary];
|
|
|
|
//init exit
|
|
self.exit = -1;
|
|
|
|
//init user id
|
|
self.uid = -1;
|
|
|
|
//init event
|
|
self.event = -1;
|
|
|
|
//set start time
|
|
self.timestamp = [NSDate date];
|
|
|
|
//set type
|
|
self.event = message->event_type;
|
|
|
|
//init
|
|
self.script = NULL;
|
|
|
|
//event specific logic
|
|
|
|
// set type
|
|
// extract (relevant) process object, etc
|
|
switch (message->event_type) {
|
|
|
|
//exec
|
|
case ES_EVENT_TYPE_AUTH_EXEC:
|
|
case ES_EVENT_TYPE_NOTIFY_EXEC:
|
|
|
|
//set process (target)
|
|
process = message->event.exec.target;
|
|
|
|
//extract/format args
|
|
[self extractArgs:&message->event];
|
|
|
|
break;
|
|
|
|
//fork
|
|
case ES_EVENT_TYPE_NOTIFY_FORK:
|
|
|
|
//set process (child)
|
|
process = message->event.fork.child;
|
|
|
|
break;
|
|
|
|
//exit
|
|
case ES_EVENT_TYPE_NOTIFY_EXIT:
|
|
|
|
//set process
|
|
process = message->process;
|
|
|
|
//set exit code
|
|
self.exit = message->event.exit.stat;
|
|
|
|
break;
|
|
|
|
//BTM add
|
|
// see if we can use instigator
|
|
case ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD:
|
|
|
|
if( (message->version >= 8) &&
|
|
(message->event.btm_launch_item_add->instigator_token) ) {
|
|
process = message->event.btm_launch_item_add->instigator;
|
|
}
|
|
|
|
if(!process) {
|
|
process = message->process;
|
|
}
|
|
|
|
break;
|
|
|
|
//default
|
|
default:
|
|
|
|
//set process
|
|
process = message->process;
|
|
|
|
break;
|
|
}
|
|
|
|
//init audit token
|
|
self.auditToken = [NSData dataWithBytes:&process->audit_token length:sizeof(audit_token_t)];
|
|
|
|
//init pid
|
|
self.pid = audit_token_to_pid(process->audit_token);
|
|
|
|
//init ppid
|
|
self.ppid = process->ppid;
|
|
|
|
//init rpid
|
|
if(message->version >= 4)
|
|
{
|
|
self.rpid = audit_token_to_pid(process->responsible_audit_token);
|
|
}
|
|
|
|
//init uuid
|
|
self.uid = audit_token_to_euid(process->audit_token);
|
|
|
|
//add cs flags
|
|
self.csFlags = [NSNumber numberWithUnsignedInt:process->codesigning_flags];
|
|
|
|
//path
|
|
self.path = convertStringToken(&process->executable->path);
|
|
|
|
//now grab inode
|
|
inode = [NSNumber numberWithUnsignedLong:[self inode]];
|
|
|
|
//now attempt to find cached process
|
|
// via inode, so will be same binary (though pid/args, etc will be different)
|
|
cachedProcess = [processesCache objectForKey:inode];
|
|
|
|
//(basic) sanity check
|
|
// make sure cs flags still match
|
|
if(YES != [self.csFlags isEqualTo:cachedProcess.csFlags])
|
|
{
|
|
//unset
|
|
cachedProcess = nil;
|
|
|
|
//remove
|
|
[processesCache removeObjectForKey:inode];
|
|
}
|
|
|
|
//generate name
|
|
if(nil == cachedProcess)
|
|
{
|
|
self.name = [self getName];
|
|
}
|
|
//from cache
|
|
else
|
|
{
|
|
self.name = cachedProcess.name;
|
|
}
|
|
|
|
//cpu type
|
|
if(nil == cachedProcess)
|
|
{
|
|
self.architecture = [self getArchitecture];
|
|
}
|
|
//from cache
|
|
else
|
|
{
|
|
self.architecture = cachedProcess.architecture;
|
|
}
|
|
|
|
//add signing id
|
|
if(nil == cachedProcess)
|
|
{
|
|
if(nil != (string = convertStringToken(&process->signing_id)))
|
|
{
|
|
//add
|
|
self.signingID = string;
|
|
}
|
|
}
|
|
//from cache
|
|
else
|
|
{
|
|
self.signingID = cachedProcess.signingID;
|
|
}
|
|
|
|
//add team id
|
|
if(nil == cachedProcess)
|
|
{
|
|
if(nil != (string = convertStringToken(&process->team_id)))
|
|
{
|
|
//add
|
|
self.teamID = string;
|
|
}
|
|
}
|
|
//from cache
|
|
else
|
|
{
|
|
self.teamID = cachedProcess.teamID;
|
|
}
|
|
|
|
//add platform binary
|
|
self.isPlatformBinary = [NSNumber numberWithBool:process->is_platform_binary];
|
|
|
|
//save cd hash
|
|
if(nil == cachedProcess)
|
|
{
|
|
self.cdHash = [NSData dataWithBytes:(const void *)process->cdhash length:sizeof(uint8_t)*CS_CDHASH_LEN];
|
|
}
|
|
//from cache
|
|
else
|
|
{
|
|
self.cdHash = cachedProcess.cdHash;
|
|
}
|
|
|
|
//when specified
|
|
// generate full code signing info
|
|
if(csNone != csOption) {
|
|
|
|
//use cached signing info (if available)
|
|
if(cachedProcess.signingInfo.count)
|
|
{
|
|
self.signingInfo = cachedProcess.signingInfo;
|
|
}
|
|
//otherwise generate
|
|
else
|
|
{
|
|
[self generateCSInfo:csOption];
|
|
}
|
|
}
|
|
|
|
//script
|
|
if(message->version >= 2)
|
|
{
|
|
//check event type
|
|
if( (ES_EVENT_TYPE_AUTH_EXEC == message->event_type) ||
|
|
(ES_EVENT_TYPE_NOTIFY_EXEC == message->event_type) )
|
|
{
|
|
//save
|
|
if(NULL != message->event.exec.script)
|
|
{
|
|
self.script = convertStringToken(&message->event.exec.script->path);
|
|
}
|
|
}
|
|
}
|
|
|
|
//no script, but this is an interpreter?
|
|
// try look up script manually, if ES didn't give us one
|
|
if( (!self.script.length) &&
|
|
(arguments.count >= 2) ) {
|
|
|
|
if( (self.signingID) &&
|
|
([interpreters containsObject:self.signingID]) ) {
|
|
|
|
NSString* cwd = nil;
|
|
NSArray* scripts = nil;
|
|
|
|
//grab CWD
|
|
if (message->version >= 3 && message->event.exec.cwd) {
|
|
cwd = convertStringToken(&message->event.exec.cwd->path);
|
|
}
|
|
|
|
//get via args
|
|
scripts = getScripts(pid, arguments, cwd);
|
|
|
|
//for now just grab ...first?
|
|
self.script = scripts.firstObject;
|
|
}
|
|
}
|
|
|
|
//enumerate ancestors
|
|
[self enumerateAncestors];
|
|
|
|
//for EXEC events
|
|
// now, cache process
|
|
if ( (message->event_type == ES_EVENT_TYPE_AUTH_EXEC) ||
|
|
(message->event_type == ES_EVENT_TYPE_NOTIFY_EXEC) ) {
|
|
|
|
if(!cachedProcess && inode.unsignedLongValue) {
|
|
[processesCache setObject:self forKey:inode];
|
|
}
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
//get's process' path
|
|
// note: path must be set
|
|
-(unsigned long)inode
|
|
{
|
|
//stat
|
|
struct stat fileStat = {0};
|
|
|
|
//inode
|
|
unsigned long iNode = 0;
|
|
|
|
//stat
|
|
if(0 == stat(self.path.fileSystemRepresentation, &fileStat))
|
|
{
|
|
//save
|
|
iNode = fileStat.st_ino;
|
|
}
|
|
|
|
return iNode;
|
|
}
|
|
|
|
//generate code signing info
|
|
// sets 'signingInfo' iVar
|
|
-(void)generateCSInfo:(NSUInteger)csOption
|
|
{
|
|
//generate via helper function
|
|
self.signingInfo = generateSigningInfo(self, csOption, kSecCSDefaultFlags);
|
|
|
|
return;
|
|
}
|
|
|
|
//get process' name
|
|
// either via app bundle, or path
|
|
-(NSString*)getName
|
|
{
|
|
//name
|
|
NSString* name = nil;
|
|
|
|
//app path
|
|
NSString* appPath = nil;
|
|
|
|
//app bundle
|
|
NSBundle* appBundle = nil;
|
|
|
|
//convert path to app path
|
|
// generally, <blah.app>/Contents/MacOS/blah
|
|
appPath = [[[self.path stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
|
|
if(YES != [appPath hasSuffix:@".app"])
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//try load bundle
|
|
// and verify it's the 'right' bundle
|
|
appBundle = [NSBundle bundleWithPath:appPath];
|
|
if( (nil != appBundle) &&
|
|
(YES == [appBundle.executablePath isEqualToString:self.path]) )
|
|
{
|
|
//grab name from app's bundle
|
|
name = [appBundle infoDictionary][@"CFBundleDisplayName"];
|
|
}
|
|
|
|
bail:
|
|
|
|
//still nil?
|
|
// just grab from path
|
|
if(nil == name)
|
|
{
|
|
//from path
|
|
name = [self.path lastPathComponent];
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
//get process' architecture
|
|
-(NSUInteger)getArchitecture
|
|
{
|
|
//architecuture
|
|
NSUInteger architecture = ArchUnknown;
|
|
|
|
//type
|
|
cpu_type_t type = -1;
|
|
|
|
//size
|
|
size_t size = 0;
|
|
|
|
//mib
|
|
int mib[CTL_MAXNAME] = {0};
|
|
|
|
//length
|
|
size_t length = CTL_MAXNAME;
|
|
|
|
//proc info
|
|
struct kinfo_proc procInfo = {0};
|
|
|
|
//get mib for 'proc_cputype'
|
|
if(noErr != sysctlnametomib("sysctl.proc_cputype", mib, &length))
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//add pid
|
|
mib[length] = self.pid;
|
|
|
|
//inc length
|
|
length++;
|
|
|
|
//init size
|
|
size = sizeof(cpu_type_t);
|
|
|
|
//get CPU type
|
|
if(noErr != sysctl(mib, (u_int)length, &type, &size, 0, 0))
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//reversing Activity Monitor
|
|
// if CPU type is CPU_TYPE_X86_64, Apple sets architecture to 'Intel'
|
|
if(CPU_TYPE_X86_64 == type)
|
|
{
|
|
//intel
|
|
architecture = ArchIntel;
|
|
|
|
//done
|
|
goto bail;
|
|
}
|
|
|
|
//reversing Activity Monitor
|
|
// if CPU type is CPU_TYPE_ARM64, Apple checks proc's p_flags
|
|
// if P_TRANSLATED is set, then they set architecture to 'Intel'
|
|
if(CPU_TYPE_ARM64 == type)
|
|
{
|
|
//default to apple
|
|
architecture = ArchAppleSilicon;
|
|
|
|
//(re)init mib
|
|
mib[0] = CTL_KERN;
|
|
mib[1] = KERN_PROC;
|
|
mib[2] = KERN_PROC_PID;
|
|
mib[3] = pid;
|
|
|
|
//(re)set length
|
|
length = 4;
|
|
|
|
//(re)set size
|
|
size = sizeof(procInfo);
|
|
|
|
//get proc info
|
|
if(noErr != sysctl(mib, (u_int)length, &procInfo, &size, NULL, 0))
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//'P_TRANSLATED' set?
|
|
// set architecture to 'Intel'
|
|
if(P_TRANSLATED == (P_TRANSLATED & procInfo.kp_proc.p_flag))
|
|
{
|
|
//intel
|
|
architecture = ArchIntel;
|
|
}
|
|
}
|
|
|
|
bail:
|
|
|
|
return architecture;
|
|
}
|
|
|
|
//extract/format args
|
|
-(void)extractArgs:(es_events_t *)event
|
|
{
|
|
//number of args
|
|
uint32_t count = 0;
|
|
|
|
//argument
|
|
NSString* argument = nil;
|
|
|
|
//get # of args
|
|
count = es_exec_arg_count(&event->exec);
|
|
if(0 == count)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//extract all args
|
|
for(uint32_t i = 0; i < count; i++)
|
|
{
|
|
//current arg
|
|
es_string_token_t currentArg = {0};
|
|
|
|
//extract current arg
|
|
currentArg = es_exec_arg(&event->exec, i);
|
|
|
|
//convert argument
|
|
argument = convertStringToken(¤tArg);
|
|
if(nil != argument)
|
|
{
|
|
//append
|
|
[self.arguments addObject:argument];
|
|
}
|
|
}
|
|
|
|
bail:
|
|
|
|
return;
|
|
}
|
|
|
|
//generate list of ancestors
|
|
// note: if possible, built off responsible pid (vs. parent)
|
|
-(void)enumerateAncestors
|
|
{
|
|
//current process id
|
|
pid_t currentPID = -1;
|
|
|
|
//parent pid
|
|
pid_t parentPID = -1;
|
|
|
|
//have rpid (from ESF)
|
|
// init parent w/ that
|
|
if(0 != self.rpid)
|
|
{
|
|
parentPID = self.rpid;
|
|
}
|
|
//no rpid
|
|
// try lookup via private API
|
|
else if(NULL != getRPID)
|
|
{
|
|
//get rpid
|
|
parentPID = getRPID(pid);
|
|
}
|
|
|
|
//couldn't find/get rPID?
|
|
// default back to using ppid
|
|
if( (parentPID <= 0) ||
|
|
(self.pid == parentPID) )
|
|
{
|
|
//use ppid
|
|
parentPID = self.ppid;
|
|
}
|
|
|
|
//add parent
|
|
[self.ancestors addObject:[NSNumber numberWithInt:parentPID]];
|
|
|
|
//set current to parent
|
|
currentPID = parentPID;
|
|
|
|
//complete ancestry
|
|
while(YES)
|
|
{
|
|
//for parent
|
|
// first try via rPID
|
|
if(NULL != getRPID)
|
|
{
|
|
//get rpid
|
|
parentPID = getRPID(currentPID);
|
|
}
|
|
|
|
//couldn't find/get rPID?
|
|
// default back to using standard method
|
|
if( (parentPID <= 0) ||
|
|
(currentPID == parentPID) )
|
|
{
|
|
//get parent pid
|
|
parentPID = getParentID(currentPID);
|
|
}
|
|
|
|
//done?
|
|
if( (parentPID <= 0) ||
|
|
(currentPID == parentPID) )
|
|
{
|
|
//bail
|
|
break;
|
|
}
|
|
|
|
//update
|
|
currentPID = parentPID;
|
|
|
|
//add
|
|
[self.ancestors addObject:[NSNumber numberWithInt:parentPID]];
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
//for pretty printing
|
|
// though we convert to JSON
|
|
-(NSString *)description
|
|
{
|
|
//description
|
|
NSMutableString* description = nil;
|
|
|
|
//cd hash
|
|
// requires formatting
|
|
NSMutableString* cdHash = nil;
|
|
|
|
//init output string
|
|
description = [NSMutableString string];
|
|
|
|
//start process
|
|
[description appendString:@"\"process\":{"];
|
|
|
|
//add pid, path, etc
|
|
[description appendFormat: @"\"pid\":%d,\"name\":\"%@\",\"path\":\"%@\",\"uid\":%d,",self.pid, self.name, self.path, self.uid];
|
|
|
|
//add cpu type
|
|
switch(self.architecture)
|
|
{
|
|
//intel
|
|
case ArchIntel:
|
|
[description appendFormat: @"\"architecture\":\"Intel\","];
|
|
break;
|
|
|
|
//apple
|
|
case ArchAppleSilicon:
|
|
[description appendFormat: @"\"architecture\":\"Apple Silicon\","];
|
|
break;
|
|
|
|
//unknown
|
|
default:
|
|
[description appendString:@"\"architecture\":\"unknown\","];
|
|
break;
|
|
}
|
|
|
|
//arguments
|
|
if(0 != self.arguments.count)
|
|
{
|
|
//start list
|
|
[description appendFormat:@"\"arguments\":["];
|
|
|
|
//add all arguments
|
|
for(NSString* argument in self.arguments)
|
|
{
|
|
//skip blank args
|
|
if(0 == argument.length) continue;
|
|
|
|
//add
|
|
[description appendFormat:@"\"%@\",", [argument stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]];
|
|
}
|
|
|
|
//remove last ','
|
|
if(YES == [description hasSuffix:@","])
|
|
{
|
|
//remove
|
|
[description deleteCharactersInRange:NSMakeRange([description length]-1, 1)];
|
|
}
|
|
|
|
//terminate list
|
|
[description appendString:@"],"];
|
|
}
|
|
//no args
|
|
else
|
|
{
|
|
//add empty list
|
|
[description appendFormat:@"\"arguments\":[],"];
|
|
}
|
|
|
|
//add ppid
|
|
[description appendFormat: @"\"ppid\":%d,", self.ppid];
|
|
|
|
//add rpdi
|
|
[description appendFormat: @"\"rpid\":%d,", self.rpid];
|
|
|
|
//add ancestors
|
|
[description appendFormat:@"\"ancestors\":["];
|
|
|
|
//add each ancestor
|
|
for(NSNumber* ancestor in self.ancestors)
|
|
{
|
|
//add
|
|
[description appendFormat:@"%d,", ancestor.unsignedIntValue];
|
|
}
|
|
|
|
//remove last ','
|
|
if(YES == [description hasSuffix:@","])
|
|
{
|
|
//remove
|
|
[description deleteCharactersInRange:NSMakeRange([description length]-1, 1)];
|
|
}
|
|
|
|
//terminate list
|
|
[description appendString:@"],"];
|
|
|
|
//signing info (reported)
|
|
[description appendString:@"\"signing info (reported)\":{"];
|
|
|
|
//add cs flags, platform binary
|
|
[description appendFormat: @"\"csFlags\":%d,\"platformBinary\":%d,", self.csFlags.intValue, self.isPlatformBinary.intValue];
|
|
|
|
//add signing id
|
|
if(0 == self.signingID.length)
|
|
{
|
|
//blank
|
|
[description appendString:@"\"signingID\":\"\","];
|
|
}
|
|
//not blank
|
|
else
|
|
{
|
|
//append
|
|
[description appendFormat:@"\"signingID\":\"%@\",", self.signingID];
|
|
}
|
|
|
|
//add team id
|
|
if(0 == self.teamID.length)
|
|
{
|
|
//blank
|
|
[description appendString:@"\"teamID\":\"\","];
|
|
}
|
|
//not blank
|
|
else
|
|
{
|
|
//append
|
|
[description appendFormat:@"\"teamID\":\"%@\",", self.teamID];
|
|
}
|
|
|
|
//alloc string for cd hash
|
|
cdHash = [NSMutableString string];
|
|
|
|
//format cd hash
|
|
[self.cdHash enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop)
|
|
{
|
|
//To print raw byte values as hex
|
|
for (NSUInteger i = 0; i < byteRange.length; ++i) {
|
|
[cdHash appendFormat:@"%02X", ((uint8_t*)bytes)[i]];
|
|
}
|
|
}];
|
|
|
|
//add cs hash
|
|
[description appendFormat:@"\"cdHash\":\"%@\"", cdHash];
|
|
|
|
//terminate dictionary
|
|
[description appendString:@"},"];
|
|
|
|
//signing info
|
|
[description appendString:@"\"signing info (computed)\":{"];
|
|
|
|
//add all key/value pairs from signing info
|
|
for(NSString* key in self.signingInfo)
|
|
{
|
|
//value
|
|
id value = self.signingInfo[key];
|
|
|
|
//handle `KEY_SIGNATURE_SIGNER`
|
|
if(YES == [key isEqualToString:KEY_SIGNATURE_SIGNER])
|
|
{
|
|
//convert to pritable
|
|
switch ([value intValue]) {
|
|
|
|
//'None'
|
|
case None:
|
|
[description appendFormat:@"\"%@\":\"%@\",", key, @"none"];
|
|
break;
|
|
|
|
//'Apple'
|
|
case Apple:
|
|
[description appendFormat:@"\"%@\":\"%@\",", key, @"Apple"];
|
|
break;
|
|
|
|
//'App Store'
|
|
case AppStore:
|
|
[description appendFormat:@"\"%@\":\"%@\",", key, @"App Store"];
|
|
break;
|
|
|
|
//'Developer ID'
|
|
case DevID:
|
|
[description appendFormat:@"\"%@\":\"%@\",", key, @"Developer ID"];
|
|
break;
|
|
|
|
//'AdHoc'
|
|
case AdHoc:
|
|
[description appendFormat:@"\"%@\":\"%@\",", key, @"AdHoc"];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
//number?
|
|
// add as is
|
|
else if(YES == [value isKindOfClass:[NSNumber class]])
|
|
{
|
|
//add
|
|
[description appendFormat:@"\"%@\":%@,", key, value];
|
|
}
|
|
//array
|
|
else if(YES == [value isKindOfClass:[NSArray class]])
|
|
{
|
|
//start
|
|
[description appendFormat:@"\"%@\":[", key];
|
|
|
|
//add each item
|
|
[value enumerateObjectsUsingBlock:^(id obj, NSUInteger index, BOOL * _Nonnull stop) {
|
|
|
|
//add
|
|
[description appendFormat:@"\"%@\"", obj];
|
|
|
|
//add ','
|
|
if(index != ((NSArray*)value).count-1)
|
|
{
|
|
//add
|
|
[description appendString:@","];
|
|
}
|
|
|
|
}];
|
|
|
|
//terminate
|
|
[description appendString:@"],"];
|
|
}
|
|
//otherwise
|
|
// just escape it
|
|
else
|
|
{
|
|
//add
|
|
[description appendFormat:@"\"%@\":\"%@\",", key, value];
|
|
}
|
|
}
|
|
|
|
//remove last ','
|
|
if(YES == [description hasSuffix:@","])
|
|
{
|
|
//remove
|
|
[description deleteCharactersInRange:NSMakeRange([description length]-1, 1)];
|
|
}
|
|
|
|
//terminate dictionary
|
|
[description appendString:@"}"];
|
|
|
|
//exit event?
|
|
// add exit code
|
|
if(ES_EVENT_TYPE_NOTIFY_EXIT == self.event)
|
|
{
|
|
//add exit
|
|
[description appendFormat:@",\"exit code\":%d", self.exit];
|
|
}
|
|
|
|
//terminate process
|
|
[description appendString:@"}"];
|
|
|
|
return description;
|
|
}
|
|
|
|
@end
|
|
|
|
//helper function
|
|
// get parent of arbitrary process
|
|
pid_t getParentID(pid_t child)
|
|
{
|
|
//parent id
|
|
pid_t parentID = -1;
|
|
|
|
//kinfo_proc struct
|
|
struct kinfo_proc processStruct = {0};
|
|
|
|
//size
|
|
size_t procBufferSize = 0;
|
|
|
|
//mib
|
|
const u_int mibLength = 4;
|
|
|
|
//syscall result
|
|
int sysctlResult = -1;
|
|
|
|
//init buffer length
|
|
procBufferSize = sizeof(processStruct);
|
|
|
|
//init mib
|
|
int mib[mibLength] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, child};
|
|
|
|
//make syscall
|
|
sysctlResult = sysctl(mib, mibLength, &processStruct, &procBufferSize, NULL, 0);
|
|
|
|
//check if got ppid
|
|
if( (noErr == sysctlResult) &&
|
|
(0 != procBufferSize) )
|
|
{
|
|
//save ppid
|
|
parentID = processStruct.kp_eproc.e_ppid;
|
|
}
|
|
|
|
return parentID;
|
|
}
|