// // File: Process.m // Project: Proc Info // // Created by: Patrick Wardle // Copyright: 2017 Objective-See // License: Creative Commons Attribution-NonCommercial 4.0 International License // #import "Signing.h" #import "procInfo.h" #import "Utilities.h" @implementation Process @synthesize pid; @synthesize exit; @synthesize path; @synthesize ppid; @synthesize ancestors; @synthesize arguments; @synthesize timestamp; @synthesize signingInfo; //init -(id)init { //init super self = [super init]; if(nil != self) { //alloc array for args arguments = [NSMutableArray array]; //alloc array for parents ancestors = [NSMutableArray array]; //alloc array for args arguments = [NSMutableArray array]; //set start time timestamp = [NSDate date]; //init pid self.pid = -1; //init ppid self.ppid = -1; //init user self.uid = -1; //init exit self.exit = -1; } return self; } //init with a pid // method will then (try) fill out rest of object -(id)init:(pid_t)processID { //init self/super self = [self init]; if(self) { //save pid self.pid = processID; //set parent self.ppid = [[self class] getParentID:self.pid]; //get path [self pathFromPid]; if(0 == self.path.length) { #ifdef DEBUG //err msg NSLog(@"ERROR: failed to find path for process %d\n", self.pid); #endif //unset self = nil; //bail goto bail; } //set args [self getArgs]; //set user [self getUser]; //enum ancestors [self enumerateAncestors]; //init binary self.binary = [[Binary alloc] init:self.path]; } bail: return self; } //get uid // sets 'user' instance var -(void)getUser { //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, self.pid}; //make syscall sysctlResult = sysctl(mib, mibLength, &processStruct, &procBufferSize, NULL, 0); //check if got ppid if( (noErr == sysctlResult) && (0 != procBufferSize) ) { //save uid self.uid = processStruct.kp_eproc.e_ucred.cr_uid; } return; } //generate list of ancestors -(void)enumerateAncestors { //current process id pid_t currentPID = -1; //parent pid pid_t parentPID = -1; //add parent if(-1 != self.ppid) { //add [self.ancestors addObject:[NSNumber numberWithInt:self.ppid]]; //set current to parent currentPID = self.ppid; } //don't know parent // just start with self else { //start w/ self currentPID = self.pid; } //add until we get to to end (pid 0) // or error out during the traversal while(YES) { //get parent pid parentPID = [Process getParentID:currentPID]; if( (0 == parentPID) || (-1 == parentPID) || (currentPID == parentPID) ) { //bail break; } //update currentPID = parentPID; //add [self.ancestors addObject:[NSNumber numberWithInt:parentPID]]; } return; } //set process's path -(void)pathFromPid { //buffer for process path char pathBuffer[PROC_PIDPATHINFO_MAXSIZE] = {0}; //status int status = -1; //'management info base' array int mib[3] = {0}; //system's size for max args int systemMaxArgs = 0; //process's args char* processArgs = NULL; //size of buffers, etc size_t size = 0; //clear out buffer bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE); //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; } //2nd: // try via process's args ('KERN_PROCARGS2') //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: //free process args if(NULL != processArgs) { //free free(processArgs); //unset processArgs = NULL; } return; } //check if process is alive -(BOOL)isProcessAlive { //ret var BOOL bIsAlive = NO; //signal status int signalStatus = -1; //send kill with 0 to determine if alive // see: http://stackoverflow.com/questions/9152979/check-if-process-exists-given-its-pid signalStatus = kill(self.pid, 0); //is alive? if( (0 == signalStatus) || ( (0 != signalStatus) && (errno != ESRCH) ) ) { //alive! bIsAlive = YES; } return bIsAlive; } //extract commandline args // saves into 'arguments' ivar -(void)getArgs { //'management info base' array int mib[3] = {0}; //system's size for max args int systemMaxArgs = 0; //process's args char* processArgs = NULL; //# of args int numberOfArgs = 0; //start of (each) arg char* argStart = NULL; //size of buffers, etc size_t size = 0; //parser pointer char* parser = NULL; //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 = malloc(systemMaxArgs); 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 number of args // at start of buffer memcpy(&numberOfArgs, processArgs, sizeof(numberOfArgs)); //init pointer to start of args // they start right after # of args parser = processArgs + sizeof(numberOfArgs); //scan until end of process's NULL-terminated path while(parser < &processArgs[size]) { //scan till NULL-terminator if(0x0 == *parser) { //end of exe name break; } //next char parser++; } //sanity check // make sure end-of-buffer wasn't reached if(parser == &processArgs[size]) { //bail goto bail; } //skip all trailing NULLs // scan will end when non-NULL is found while(parser < &processArgs[size]) { //scan till NULL-terminator if(0x0 != *parser) { //ok, got to argv[0] break; } //next char parser++; } //sanity check // (again), make sure end-of-buffer wasn't reached if(parser == &processArgs[size]) { //bail goto bail; } //parser should now point to argv[0], process name // init arg start argStart = parser; //keep scanning until all args are found // each is NULL-terminated while(parser < &processArgs[size]) { //each arg is NULL-terminated // so scan till NULL, then save into array if(*parser == '\0') { //save arg if(NULL != argStart) { //save [self.arguments addObject:[NSString stringWithUTF8String:argStart]]; } //init string pointer to (possibly) next arg argStart = ++parser; //bail if we've hit arg cnt if(self.arguments.count == numberOfArgs) { //bail break; } } //next char parser++; } bail: //free process args if(NULL != processArgs) { //free free(processArgs); //unset processArgs = NULL; } 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: %@\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 +(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; } @end