mirror of
https://github.com/objective-see/TaskExplorer.git
synced 2026-03-22 07:02:39 +00:00
620 lines
16 KiB
Objective-C
620 lines
16 KiB
Objective-C
//
|
|
// remoteTaskService.m
|
|
// remoteTaskService
|
|
//
|
|
// Created by Patrick Wardle on 5/27/15.
|
|
// Copyright (c) 2015 Patrick Wardle. All rights reserved.
|
|
//
|
|
|
|
#import "Consts.h"
|
|
#import "Utilities.h"
|
|
#import "remoteTaskService.h"
|
|
|
|
#import <syslog.h>
|
|
#import <libproc.h>
|
|
#import <arpa/inet.h>
|
|
#import <sys/sysctl.h>
|
|
#import <mach/mach_vm.h>
|
|
#import <mach-o/dyld_images.h>
|
|
|
|
//32bit
|
|
struct dyld_image_info_32 {
|
|
int imageLoadAddress;
|
|
int imageFilePath;
|
|
int imageFileModDate;
|
|
};
|
|
|
|
@implementation remoteTaskService
|
|
|
|
+(remoteTaskService *)defaultService
|
|
{
|
|
static dispatch_once_t onceToken;
|
|
static remoteTaskService *shared;
|
|
dispatch_once(&onceToken, ^{
|
|
shared = [remoteTaskService new];
|
|
});
|
|
return shared;
|
|
}
|
|
|
|
//get task's commandline args
|
|
// ->args returned in array arg
|
|
-(void)getTaskArgs:(NSNumber*)taskPID withReply:(void (^)(NSMutableArray *))reply
|
|
{
|
|
//task's args
|
|
NSMutableArray* arguments = nil;
|
|
|
|
//'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;
|
|
|
|
//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;
|
|
|
|
//alloc array for args
|
|
arguments = [NSMutableArray array];
|
|
|
|
//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] = taskPID.intValue;
|
|
|
|
//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
|
|
[arguments addObject:[NSString stringWithUTF8String:taskArgs + sizeof(int)]];
|
|
|
|
//init point to start of args
|
|
// ->they start right after # of args
|
|
parser = taskArgs + sizeof(numberOfArgs);
|
|
|
|
//scan until end of task's NULL-terminated path
|
|
while(parser < &taskArgs[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 == &taskArgs[size])
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//skip all trailing NULLs
|
|
// ->scan will non-NULL is found
|
|
while(parser < &taskArgs[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 == &taskArgs[size])
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//parser should now point to argv[0], task name
|
|
// ->init arg start
|
|
argStart = parser;
|
|
|
|
//keep scanning until all args are found
|
|
// ->each is NULL-terminated
|
|
while(parser < &taskArgs[size])
|
|
{
|
|
//each arg is NULL-terminated
|
|
// ->so scan till NULL, then save into array
|
|
if(*parser == '\0')
|
|
{
|
|
//save arg
|
|
if(NULL != argStart)
|
|
{
|
|
//save
|
|
[arguments addObject:[NSString stringWithUTF8String:argStart]];
|
|
}
|
|
|
|
//init string pointer to (possibly) next arg
|
|
argStart = ++parser;
|
|
|
|
//bail if we've hit arg cnt
|
|
// ->note: added full process path as faux arg[0], so add 1
|
|
if(arguments.count == numberOfArgs + 1)
|
|
{
|
|
//bail
|
|
break;
|
|
}
|
|
}
|
|
|
|
//next char
|
|
parser++;
|
|
}
|
|
|
|
//bail
|
|
bail:
|
|
|
|
//free process args
|
|
if(NULL != taskArgs)
|
|
{
|
|
//free
|
|
free(taskArgs);
|
|
|
|
//reset
|
|
taskArgs = NULL;
|
|
}
|
|
|
|
//invoke reply block
|
|
reply(arguments);
|
|
|
|
return;
|
|
}
|
|
|
|
//enumerate dylibs for a specified task
|
|
-(void)enumerateDylibs:(NSNumber*)pid withReply:(void (^)(NSMutableArray *))reply
|
|
{
|
|
//dylibs
|
|
NSMutableArray* dylibs = nil;
|
|
|
|
//results from 'file' cmd
|
|
NSMutableDictionary* results = nil;
|
|
|
|
//output (stdout) from 'file' cmd
|
|
NSString* output = nil;
|
|
|
|
//path offset
|
|
NSRange pathOffset = {0};
|
|
|
|
//dylib
|
|
NSString* dylib = nil;
|
|
|
|
//skip self
|
|
// can't vmmap self :|
|
|
if(pid.intValue == getpid())
|
|
{
|
|
goto bail;
|
|
}
|
|
|
|
//alloc array for dylibs
|
|
dylibs = [NSMutableArray array];
|
|
|
|
//exec vmmap
|
|
results = execTask(VMMAP, @[@"-w", [pid stringValue]], YES);
|
|
if( (nil == results[EXIT_CODE]) ||
|
|
(0 != [results[EXIT_CODE] integerValue]) )
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//convert stdout data to string
|
|
output = [[NSString alloc] initWithData:results[STDOUT] encoding:NSUTF8StringEncoding];
|
|
|
|
//iterate over all results
|
|
// line by line, looking for '__TEXT'
|
|
for(NSString* line in [output componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]])
|
|
{
|
|
//ignore any line that doesn't start with '__TEXT'
|
|
if(YES != [line hasPrefix:@"__TEXT"])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//format of line is: __TEXT 00007fff63564000-00007fff6359b000 [ 220K] r-x/rwx SM=COW /usr/lib/dyld
|
|
// ->grab path, by finding: ' /'
|
|
pathOffset = [line rangeOfString:@" /"];
|
|
|
|
//sanity check
|
|
// ->make sure path was found
|
|
if(NSNotFound == pathOffset.location)
|
|
{
|
|
//not found
|
|
continue;
|
|
}
|
|
|
|
//extract dylib's path
|
|
// ->trim leading whitespace
|
|
dylib = [[line substringFromIndex:pathOffset.location] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
|
|
|
//sanity check
|
|
if(nil == dylib)
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//add to results array
|
|
[dylibs addObject:dylib];
|
|
}
|
|
|
|
//remove dups
|
|
[dylibs setArray:[[[NSSet setWithArray:dylibs] allObjects] mutableCopy]];
|
|
|
|
//send back
|
|
reply(dylibs);
|
|
|
|
bail:
|
|
|
|
return;
|
|
}
|
|
|
|
//enumerate open files
|
|
// ->accomplish this via lsof, since proc_pidinfo() misses some files...
|
|
-(void)enumerateFiles:(NSNumber*)taskPID withReply:(void (^)(NSMutableArray *))reply
|
|
{
|
|
//results
|
|
NSMutableDictionary* results = nil;
|
|
|
|
//results split on '\n'
|
|
NSArray* splitResults = nil;
|
|
|
|
//file path
|
|
NSString* filePath = nil;
|
|
|
|
//file info dictionary
|
|
NSMutableDictionary* fileInfo = nil;
|
|
|
|
//just file paths
|
|
// ->helps detect/ignore dups
|
|
NSMutableArray* filePaths = nil;
|
|
|
|
//unique files
|
|
NSMutableArray* files = nil;
|
|
|
|
//init array for file paths
|
|
filePaths = [NSMutableArray array];
|
|
|
|
//init array for unqiue files
|
|
files = [NSMutableArray array];
|
|
|
|
//exec 'file' to get file type
|
|
results = execTask(LSOF, @[@"-Fn", @"-p", taskPID.stringValue], YES);
|
|
if( (nil == results[EXIT_CODE]) ||
|
|
(0 != [results[EXIT_CODE] integerValue]) )
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//split results into array
|
|
splitResults = [[[NSString alloc] initWithData:results[STDOUT] encoding:NSUTF8StringEncoding] componentsSeparatedByString:@"\n"];
|
|
if( (nil == splitResults) ||
|
|
(0 == splitResults.count) )
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//iterate over all results
|
|
// ->make file info dictionary for files (not sockets, etc)
|
|
for(NSString* result in splitResults)
|
|
{
|
|
//skip any odd/weird/short lines
|
|
// lsof outpupt will be in format: 'n<filePath'>
|
|
if( (YES != [result hasPrefix:@"n"]) ||
|
|
(result.length < 0x2) )
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//init file path
|
|
// ->result, minus first (lsof-added) char
|
|
filePath = [result substringFromIndex:0x1];
|
|
|
|
//skip 'non files'
|
|
if(YES != [[NSFileManager defaultManager] fileExistsAtPath:filePath])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//also skip files such as '/', /dev/null, etc
|
|
if( (YES == [filePath isEqualToString:@"/"]) ||
|
|
(YES == [filePath isEqualToString:@"/dev/null"]) )
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//also avoid duplicates
|
|
if(YES == [filePaths containsObject:filePath])
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//alloc info dictionary
|
|
fileInfo = [NSMutableDictionary dictionary];
|
|
|
|
//add path
|
|
fileInfo[KEY_FILE_PATH] = filePath;
|
|
|
|
//save
|
|
[files addObject:fileInfo];
|
|
|
|
//add to list of paths
|
|
// ->prevents dups
|
|
[filePaths addObject:filePath];
|
|
}
|
|
|
|
//bail
|
|
bail:
|
|
|
|
//invoke reply
|
|
reply(files);
|
|
|
|
return;
|
|
}
|
|
|
|
//TODO: soi_rcv/soi_snd to get packets!?
|
|
//enumerate network connections
|
|
-(void)enumerateNetwork:(NSNumber*)taskPID withReply:(void (^)(NSMutableArray *))reply
|
|
{
|
|
//task's sockets
|
|
NSMutableArray* sockets = nil;
|
|
|
|
//socket info dictionary
|
|
NSMutableDictionary* socket = nil;
|
|
|
|
//size
|
|
int bufferSize = -1;
|
|
|
|
//proc handles
|
|
struct proc_fdinfo *procFDInfo = NULL;
|
|
|
|
//number of handle
|
|
int numberOfProcFDs = 0;
|
|
|
|
//socket info struct
|
|
struct socket_fdinfo socketInfo = {0};
|
|
|
|
//socket local addr
|
|
// ->big enough for both IPv4 and IPv6
|
|
char localIPAddr[INET6_ADDRSTRLEN] = {0};
|
|
|
|
//socket remote addr
|
|
// ->big enough for both IPv4 and IPv6
|
|
char remoteIPAddr[INET6_ADDRSTRLEN] = {0};
|
|
|
|
//socket remote port
|
|
short remotePort = 0;
|
|
|
|
//alloc array for sockets
|
|
sockets = [NSMutableArray array];
|
|
|
|
//invoke proc_pidinfo w/ NULL
|
|
// ->get's required buffer size
|
|
bufferSize = proc_pidinfo([taskPID intValue], PROC_PIDLISTFDS, 0, 0, 0);
|
|
if(bufferSize <= 0)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//alloc buffer for handles
|
|
procFDInfo = (struct proc_fdinfo *)malloc(bufferSize);
|
|
|
|
//sanity check
|
|
if(NULL == procFDInfo)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//get proc's handles
|
|
bufferSize = proc_pidinfo([taskPID intValue], PROC_PIDLISTFDS, 0, procFDInfo, bufferSize);
|
|
if(bufferSize <= 0)
|
|
{
|
|
//bail
|
|
goto bail;
|
|
}
|
|
|
|
//calc number of handles
|
|
numberOfProcFDs = bufferSize / PROC_PIDLISTFD_SIZE;
|
|
|
|
//iterate over all file descriptors
|
|
// ->only care about sockets though...
|
|
for(NSUInteger i = 0; i < numberOfProcFDs; i++)
|
|
{
|
|
//skip all non-sockets
|
|
if(PROX_FDTYPE_SOCKET != procFDInfo[i].proc_fdtype)
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//get (detailed) info about socket
|
|
// ->should return # of bytes that matches size of socket struct
|
|
if(sizeof(struct socket_fdinfo) != proc_pidfdinfo([taskPID intValue], procFDInfo[i].proc_fd, PROC_PIDFDSOCKETINFO, &socketInfo, sizeof(struct socket_fdinfo)))
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//skip any non-internet sockets
|
|
if( (socketInfo.psi.soi_family != AF_INET) &&
|
|
(socketInfo.psi.soi_family != AF_INET6) )
|
|
{
|
|
//skip
|
|
continue;
|
|
}
|
|
|
|
//alloc dictionary for socket
|
|
socket = [NSMutableDictionary dictionary];
|
|
|
|
//add local port
|
|
socket[KEY_LOCAL_PORT] = [NSNumber numberWithShort:ntohs(socketInfo.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport)];
|
|
|
|
//get remote port
|
|
remotePort = ntohs(socketInfo.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport);
|
|
|
|
//IPv4 sockets
|
|
if(socketInfo.psi.soi_family == AF_INET)
|
|
{
|
|
//get local ip addr
|
|
inet_ntop(AF_INET, &socketInfo.psi.soi_proto.pri_tcp.tcpsi_ini.insi_laddr.ina_46.i46a_addr4, localIPAddr, sizeof(localIPAddr));
|
|
|
|
//add local ip addr
|
|
socket[KEY_LOCAL_ADDR] = [NSString stringWithUTF8String:localIPAddr];
|
|
|
|
//for connected sessions
|
|
// ->get/save remote ip addr
|
|
if(0 != remotePort)
|
|
{
|
|
//add remote port
|
|
socket[KEY_REMOTE_PORT] = [NSNumber numberWithShort:remotePort];
|
|
|
|
//get remote ip addr
|
|
inet_ntop(AF_INET, &socketInfo.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr.ina_46.i46a_addr4, remoteIPAddr, sizeof(remoteIPAddr));
|
|
|
|
//add remote ip addr
|
|
socket[KEY_REMOTE_ADDR] = [NSString stringWithUTF8String:remoteIPAddr];
|
|
}
|
|
}
|
|
//IPv6 sockets
|
|
else if(socketInfo.psi.soi_family == AF_INET6)
|
|
{
|
|
//get local ip addr
|
|
inet_ntop(AF_INET6, &socketInfo.psi.soi_proto.pri_tcp.tcpsi_ini.insi_laddr.ina_6, localIPAddr, sizeof(localIPAddr));
|
|
|
|
//TODO: ::1 -> 'loopback' or 0:0:0:0:0:0:0:1
|
|
// or ::0, 'unspecified' (see: https://en.wikipedia.org/wiki/IPv6_address)
|
|
|
|
//add local ip addr
|
|
socket[KEY_LOCAL_ADDR] = [NSString stringWithUTF8String:localIPAddr];
|
|
|
|
//for connected sessions
|
|
// ->get remote ip addr
|
|
if(0 != remotePort)
|
|
{
|
|
//add remote port
|
|
socket[KEY_REMOTE_PORT] = [NSNumber numberWithShort:remotePort];
|
|
|
|
//get remote ip addr
|
|
inet_ntop(AF_INET6, &socketInfo.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr.ina_6, remoteIPAddr, sizeof(remoteIPAddr));
|
|
|
|
//add remote ip addr
|
|
socket[KEY_REMOTE_ADDR] = [NSString stringWithUTF8String:remoteIPAddr];
|
|
}
|
|
}
|
|
|
|
//set type
|
|
socket[KEY_SOCKET_TYPE] = [NSNumber numberWithInt:socketInfo.psi.soi_type];
|
|
|
|
//set family
|
|
// ->for now this will only be 'AF_INET' or 'AF_INET6'
|
|
socket[KEY_SOCKET_FAMILY] = [NSNumber numberWithInt:socketInfo.psi.soi_family];
|
|
|
|
//set protocol
|
|
socket[KEY_SOCKET_PROTO] = [NSNumber numberWithInt:socketInfo.psi.soi_protocol];
|
|
|
|
//get state
|
|
// ->only for stream stockets though
|
|
if(SOCK_STREAM == socketInfo.psi.soi_type)
|
|
{
|
|
//set state
|
|
socket[KEY_SOCKET_STATE] = [NSNumber numberWithInt:socketInfo.psi.soi_proto.pri_tcp.tcpsi_state];
|
|
}
|
|
|
|
//add
|
|
[sockets addObject:socket];
|
|
|
|
}//all FDs
|
|
|
|
//bail
|
|
bail:
|
|
|
|
//free buffer
|
|
if(nil != procFDInfo)
|
|
{
|
|
//free
|
|
free(procFDInfo);
|
|
}
|
|
|
|
//invoke reply
|
|
reply(sockets);
|
|
|
|
return;
|
|
}
|
|
|
|
@end
|
|
|
|
|