// // Utilities.m // WhatsYourSign // // Created by Patrick Wardle on 7/7/16. // Copyright (c) 2016 Objective-See. All rights reserved. // #import "Consts.h" #import "Logging.h" #import "Utilities.h" #import #import #import #import #import #import #import #import #import //get OS version NSDictionary* getOSVersion() { //os version info NSMutableDictionary* osVersionInfo = nil; //major v SInt32 majorVersion = 0; //minor v SInt32 minorVersion = 0; //alloc dictionary osVersionInfo = [NSMutableDictionary dictionary]; //get major version if(STATUS_SUCCESS != Gestalt(gestaltSystemVersionMajor, &majorVersion)) { //reset osVersionInfo = nil; //bail goto bail; } //get minor version if(STATUS_SUCCESS != Gestalt(gestaltSystemVersionMinor, &minorVersion)) { //reset osVersionInfo = nil; //bail goto bail; } //set major version osVersionInfo[@"majorVersion"] = [NSNumber numberWithInteger:majorVersion]; //set minor version osVersionInfo[@"minorVersion"] = [NSNumber numberWithInteger:minorVersion]; //bail bail: return osVersionInfo; } //get app's version // ->extracted from Info.plist NSString* getAppVersion() { //read and return 'CFBundleVersion' from bundle return [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleVersion"]; } //given a path to binary // parse it back up to find app's bundle NSBundle* findAppBundle(NSString* binaryPath) { //app's bundle NSBundle* appBundle = nil; //app's path NSString* appPath = nil; //first just try full path appPath = binaryPath; //try to find the app's bundle/info dictionary do { //try to load app's bundle appBundle = [NSBundle bundleWithPath:appPath]; //check for match // ->binary path's match if( (nil != appBundle) && (YES == [appBundle.executablePath isEqualToString:binaryPath])) { //all done break; } //always unset bundle var since it's being returned // ->and at this point, its not a match appBundle = nil; //remove last part // ->will try this next appPath = [appPath stringByDeletingLastPathComponent]; //scan until we get to root // ->of course, loop will exit if app info dictionary is found/loaded } while( (nil != appPath) && (YES != [appPath isEqualToString:@"/"]) && (YES != [appPath isEqualToString:@""]) ); return appBundle; } //set dir's|file's group/owner BOOL setFileOwner(NSString* path, NSNumber* groupID, NSNumber* ownerID, BOOL recursive) { //ret var BOOL bSetOwner = NO; //owner dictionary NSDictionary* fileOwner = nil; //sub paths NSArray *subPaths = nil; //full path // ->for recursive NSString* fullPath = nil; //init permissions dictionary fileOwner = @{NSFileGroupOwnerAccountID:groupID, NSFileOwnerAccountID:ownerID}; //set group/owner if(YES != [[NSFileManager defaultManager] setAttributes:fileOwner ofItemAtPath:path error:NULL]) { //err msg logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set ownership for %@ (%@)", path, fileOwner]); //bail goto bail; } //dbg msg logMsg(LOG_DEBUG, [NSString stringWithFormat:@"set ownership for %@ (%@)", path, fileOwner]); //do it recursively if(YES == recursive) { //sanity check // ->make sure root starts with '/' if(YES != [path hasSuffix:@"/"]) { //add '/' path = [NSString stringWithFormat:@"%@/", path]; } //get all subpaths subPaths = [[NSFileManager defaultManager] subpathsAtPath:path]; for(NSString *subPath in subPaths) { //init full path fullPath = [path stringByAppendingString:subPath]; //set group/owner if(YES != [[NSFileManager defaultManager] setAttributes:fileOwner ofItemAtPath:fullPath error:NULL]) { //err msg logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set ownership for %@ (%@)", fullPath, fileOwner]); //bail goto bail; } } } //no errors bSetOwner = YES; //bail bail: return bSetOwner; } //set permissions for file BOOL setFilePermissions(NSString* file, int permissions, BOOL recursive) { //ret var BOOL bSetPermissions = NO; //file permissions NSDictionary* filePermissions = nil; //root directory NSURL* root = nil; //directory enumerator NSDirectoryEnumerator* enumerator = nil; //error NSError* error = nil; //init dictionary filePermissions = @{NSFilePosixPermissions: [NSNumber numberWithInt:permissions]}; //apply file permissions recursively if(YES == recursive) { //init root root = [NSURL fileURLWithPath:file]; //init enumerator enumerator = [[NSFileManager defaultManager] enumeratorAtURL:root includingPropertiesForKeys:[NSArray arrayWithObject:NSURLIsDirectoryKey] options:0 errorHandler:^(NSURL *url, NSError *error) { return YES; }]; //set file permissions on each for(NSURL* currentFile in enumerator) { //set permissions if(YES != [[NSFileManager defaultManager] setAttributes:filePermissions ofItemAtPath:currentFile.path error:&error]) { //err msg logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set permissions for %@ (%@), %@", currentFile.path, filePermissions, error]); //bail goto bail; } } } //always set permissions on passed in file (or top-level directory) // ->note: recursive enumerator skips root directory, so execute this always if(YES != [[NSFileManager defaultManager] setAttributes:filePermissions ofItemAtPath:file error:NULL]) { //err msg logMsg(LOG_ERR, [NSString stringWithFormat:@"failed to set permissions for %@ (%@)", file, filePermissions]); //bail goto bail; } //happy bSetPermissions = YES; //bail bail: return bSetPermissions; } //exec a process and grab it's output NSData* execTask(NSString* binaryPath, NSArray* arguments) { //task NSTask *task = nil; //output pipe NSPipe *outPipe = nil; //read handle NSFileHandle* readHandle = nil; //output NSMutableData *output = nil; //init task task = [NSTask new]; //init output pipe outPipe = [NSPipe pipe]; //init read handle readHandle = [outPipe fileHandleForReading]; //init output buffer output = [NSMutableData data]; //set task's path [task setLaunchPath:binaryPath]; //set task's args [task setArguments:arguments]; //set task's output [task setStandardOutput:outPipe]; //wrap task launch @try { //launch [task launch]; } @catch(NSException* exception) { //bail goto bail; } //read in output while(YES == [task isRunning]) { //accumulate output [output appendData:[readHandle readDataToEndOfFile]]; } //grab any left over data [output appendData:[readHandle readDataToEndOfFile]]; //bail bail: return output; } //get OS's major or minor version SInt32 getVersion(OSType selector) { //version // ->major or minor SInt32 version = -1; //get version info if(noErr != Gestalt(selector, &version)) { //reset version version = -1; //err goto bail; } //bail bail: return version; } //get process's path NSString* getProcessPath(pid_t pid) { //task path NSString* taskPath = nil; //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* taskArgs = NULL; //# of args int numberOfArgs = 0; //size of buffers, etc size_t size = 0; //reset buffer bzero(pathBuffer, PROC_PIDPATHINFO_MAXSIZE); //first attempt to get path via 'proc_pidpath()' status = proc_pidpath(pid, pathBuffer, sizeof(pathBuffer)); if(0 != status) { //init task's name taskPath = [NSString stringWithUTF8String:pathBuffer]; } //otherwise // ->try via task'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)) { //bail goto bail; } //alloc space for args taskArgs = malloc(systemMaxArgs); if(NULL == taskArgs) { //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, taskArgs, &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, taskArgs, sizeof(numberOfArgs)); //extract task's name // ->follows # of args (int) and is NULL-terminated taskPath = [NSString stringWithUTF8String:taskArgs + sizeof(int)]; } //bail bail: //free process args if(NULL != taskArgs) { //free free(taskArgs); //reset taskArgs = NULL; } return taskPath; } //given a pid // ->get the name of the process NSString* getProcessName(pid_t pid) { //task path NSString* processName = nil; //process path NSString* processPath = nil; //app's bundle NSBundle* appBundle = nil; //get process path processPath = getProcessPath(pid); if( (nil == processPath) || (0 == processPath.length) ) { //default to 'unknown' processName = @""; //bail goto bail; } //try find an app bundle appBundle = findAppBundle(processPath); if(nil != appBundle) { //grab name from app's bundle processName = [appBundle infoDictionary][@"CFBundleName"]; } //still nil? // ->just grab from path if(nil == processName) { //from path processName = [processPath lastPathComponent]; } //bail bail: return processName; } //given a process name // ->get the (first) instance of that process pid_t getProcessID(NSString* processName) { //status int status = -1; //process id pid_t processID = -1; //# of procs int numberOfProcesses = 0; //array of pids pid_t* pids = NULL; //get # of procs numberOfProcesses = proc_listpids(PROC_ALL_PIDS, 0, NULL, 0); //alloc buffer for pids pids = calloc(numberOfProcesses, sizeof(pid_t)); //get list of pids status = proc_listpids(PROC_ALL_PIDS, 0, pids, numberOfProcesses * sizeof(pid_t)); if(status < 0) { //err //syslog(LOG_ERR, "OBJECTIVE-SEE ERROR: proc_listpids() failed with %d", status); //bail goto bail; } //iterate over all pids // ->get name for each for(int i = 0; i < numberOfProcesses; ++i) { //skip blank pids if(0 == pids[i]) { //skip continue; } //skip if name doesn't match if(YES != [processName isEqualToString:getProcessName(pids[i])]) { //next continue; } //got match processID = pids[i]; //exit loop break; } //bail bail: //free buffer if(NULL != pids) { //free free(pids); //reset pids = NULL; } return processID; } //determine if there is a new version // -1, YES or NO NSInteger isNewVersion(NSMutableString* versionString) { //flag NSInteger newVersionExists = -1; //installed version NSString* installedVersion = nil; //latest version NSString* latestVersion = nil; //get installed version installedVersion = getAppVersion(); //get latest version // ->will query internet (bb's website) latestVersion = getLatestVersion(); if(nil == latestVersion) { //set error msg [versionString setString:@"failed to get latest version"]; //bail goto bail; } //save version [versionString setString:latestVersion]; //set version flag // ->YES/NO newVersionExists = (NSOrderedAscending == [installedVersion compare:latestVersion options:NSNumericSearch]); //bail bail: return newVersionExists; } //query interwebz to get latest version NSString* getLatestVersion() { //version data NSData* versionData = nil; //version dictionary NSDictionary* versionDictionary = nil; //latest version NSString* latestVersion = nil; //get version from remote URL versionData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:PRODUCT_VERSION_URL]]; //sanity check if(nil == versionData) { //bail goto bail; } //convert JSON to dictionary versionDictionary = [NSJSONSerialization JSONObjectWithData:versionData options:0 error:nil]; //sanity check if(nil == versionDictionary) { //bail goto bail; } //extract latest version latestVersion = versionDictionary[@"latestVersion"]; //bail bail: return latestVersion; } //wait until a window is non nil // ->then make it modal void makeModal(NSWindowController* windowController) { //wait up to 1 second window to be non-nil // ->then make modal for(int i=0; i<20; i++) { //can make it modal once we have a window if(nil != windowController.window) { //make modal on main thread dispatch_sync(dispatch_get_main_queue(), ^{ //modal [[NSApplication sharedApplication] runModalForWindow:windowController.window]; }); //all done break; } //nap [NSThread sleepForTimeInterval:0.05f]; }//until 1 second return; }