├── .gitignore ├── README.md ├── Src ├── ProcLib │ ├── ProcLib.h │ └── module.modulemap ├── args.swift ├── colors.swift ├── launchdXPC │ ├── launchdXPC.h │ ├── launchdXPC.m │ └── module.modulemap ├── main.swift ├── network.swift ├── node.swift └── process.swift ├── TrueTree.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist └── license.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | ## Build generated 3 | build/ 4 | DerivedData/ 5 | ## Various settings 6 | *.pbxuser 7 | xcuserdata/ 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TrueTree 2 | 3 |

4 | 5 | **About** \ 6 | Read all about the TrueTree concept [here](https://themittenmac.com/the-truetree-concept/) 7 | 8 | 9 | TrueTree is more than just a pstree command for macOS. It is used to display a process tree for current running processes while using a hierarchy built on additoinal pids that can be collected from the operating system. The standard process tree on macOS that can be built with traditional pids and ppids is less than helpful on macOS due to all the XPC communication at play. The vast majority of processes end up having a parent process of launchd. TrueTree however displays a process tree that is meant to be useful to incident responders, threat hunters, researchers, and everything in between! 10 | 11 | Major Update: 12 | Because of the ever changing features on macOS, since macOS 11 some features don't operate quite the same as they used to when I wrote the original blog post for TrueTree. Apple has introduced the addition of a new process "runningboardd" which ends up being the true parent of many processes. To get around this TrueTree was updated to use the Application Services framework which allowed for aquiring the true parent in some scenarios. However, this no longer allows for the aquiring of a true parent process if that parent process has terminated. Making it difficult to pinpoint some true parents such as the "open" command. A small price to pay to ensure TrueTree continues to operate across macOS releases. Some other signs of a true parent can often be found inside of the launchctl procinfo output. This might be taken into consideration in the future. 13 | 14 | **./TrueTree -h** \ 15 | --nocolor -> Do not color code items in output 16 | --timeline -> Sort and print all processes by their creation timestamp 17 | --timestamps -> Include process timestamps 18 | --standard -> Print the standard Unix tree instead of TrueTree 19 | --sources -> Print the source of where each processes parent came from 20 | --nonetwork -> Do not print network connection 21 | --nopid -> Do not print the pid next to each process 22 | --nopath -> Print process name only instead of full paths 23 | --version -> Print the TrueTree version number 24 | -o -> output to file 25 | 26 | Note: Requires Root 27 | 28 | **./TrueTree** \ 29 | Displays an enhanced process tree using the TrueTree concept 30 |

31 |

32 | 33 | **./TrueTree --standard** \ 34 | For tree output based on standard pids and ppids use --standard 35 |

36 |

37 | 38 | **./TrueTree --timestamps** \ 39 | For output in either format with process create time added use the --timestamps option 40 |

41 |

42 | 43 | **./TrueTree --sources** \ 44 | To show where each parent pid was aquired from use the --sources option 45 |

46 |

47 | 48 | **./TrueTree --timeline** \ 49 | Does not collect a tree. Instead just prints processes sorted by creation time 50 |

51 | -------------------------------------------------------------------------------- /Src/ProcLib/ProcLib.h: -------------------------------------------------------------------------------- 1 | #ifndef ProcLib_h 2 | #define ProcLib_h 3 | 4 | #include 5 | 6 | #endif /* ProcLib_h */ 7 | -------------------------------------------------------------------------------- /Src/ProcLib/module.modulemap: -------------------------------------------------------------------------------- 1 | module ProcLib { 2 | header "ProcLib.h" 3 | link "libproc" 4 | } 5 | -------------------------------------------------------------------------------- /Src/args.swift: -------------------------------------------------------------------------------- 1 | // 2 | // args.swift 3 | // TrueTree 4 | // 5 | // Created by Jaron Bradley on 2/20/20. 6 | // 2020 TheMittenMac 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | class ArgManager { 13 | 14 | var color = true 15 | var timestamps = false 16 | var standardMode = false 17 | var sources = false 18 | var timelineMode = true 19 | var network = true 20 | var showpid = true 21 | var showpath = true 22 | var toFile: String? 23 | let availableArgs = ["--nocolor", "--timeline", "--timestamps", "-o", "--standard", "--version", "--sources", "--nonetwork", "--nopid", "--nopath"] 24 | 25 | init(suppliedArgs: [String]) { 26 | setArgs(suppliedArgs) 27 | } 28 | 29 | func setArgs(_ args:[String]) { 30 | for (x,arg) in (args).enumerated() { 31 | if x == 0 || !arg.starts(with: "-") { 32 | continue 33 | } else if arg == "-h" || arg == "--help" { 34 | self.printHelp() 35 | } else if arg == "--nocolor" { 36 | color.toggle() 37 | } else if arg == "--timestamps" { 38 | timestamps.toggle() 39 | } else if arg == "--standard" { 40 | standardMode.toggle() 41 | } else if arg == "--sources" { 42 | sources.toggle() 43 | } else if arg == "--timeline" { 44 | timelineMode.toggle() 45 | } else if arg == "--nonetwork" { 46 | network.toggle() 47 | } else if arg == "--nopid" { 48 | showpid.toggle() 49 | } else if arg == "--nopath" { 50 | showpath.toggle() 51 | } else if arg == "--version" { 52 | print(version) 53 | exit(0) 54 | } else if arg == "-o" { 55 | if args.count > x+1 && !availableArgs.contains(args[x+1]) { 56 | toFile = args[x+1] 57 | } else { 58 | toFile = "tree_output.txt" 59 | } 60 | } else { 61 | print("Unidentified argument " + arg) 62 | exit(1) 63 | } 64 | } 65 | } 66 | 67 | func printHelp() { 68 | print("--nocolor Do not color code items in output") 69 | print("--timeline Sort and print all processes by their creation timestamp (Non-Tree Mode)") 70 | print("--timestamps Include process timestamps") 71 | print("--standard Print the standard Unix tree instead of TrueTree") 72 | print("--sources Print the source of where each processes parent came from") 73 | print("--nonetwork Do not print network connection") 74 | print("--nopid Do not print the pid next to each process") 75 | print("--nopath Print process name only instead of full paths") 76 | print("--version Print the TrueTree version number") 77 | print("-o Output to file") 78 | exit(0) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Src/colors.swift: -------------------------------------------------------------------------------- 1 | // 2 | // colors.swift 3 | // TrueTree 4 | // 5 | // Created by Jaron Bradley on 12/2/22. 6 | // Copyright © 2022 mittenmac. All rights reserved. 7 | // 8 | 9 | extension String { 10 | 11 | var blue: String { 12 | return "\u{001B}[0;34m\(self)\u{001B}[0;0m" 13 | } 14 | 15 | var magenta: String { 16 | return "\u{001B}[0;35m\(self)\u{001B}[0;0m" 17 | } 18 | 19 | var cyan: String { 20 | return "\u{001B}[0;36m\(self)\u{001B}[0;0m" 21 | } 22 | 23 | var red: String { 24 | return "\u{001B}[0;31m\(self)\u{001B}[0;0m" 25 | } 26 | 27 | var yellow: String { 28 | return "\u{001B}[0;33m\(self)\u{001B}[0;0m" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Src/launchdXPC/launchdXPC.h: -------------------------------------------------------------------------------- 1 | // 2 | // launchdXPC.h 3 | // 4 | // Created by Patrick Wardle 5 | // Ported from code by Jonathan Levin 6 | // 7 | 8 | #ifndef launchdXPC_h 9 | #define launchdXPC_h 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #import 16 | 17 | int getSubmittedPid(int pid); 18 | 19 | //hit up launchd (via XPC) to get process info 20 | NSString* getSubmittedByPlist(unsigned long pid); 21 | 22 | //launchd structs/functions 23 | // inspired by: http://newosxbook.com/articles/jlaunchctl.html 24 | struct xpc_global_data { 25 | 26 | uint64_t a; 27 | uint64_t xpc_flags; 28 | mach_port_t task_bootstrap_port; 29 | #ifndef _64 30 | uint32_t padding; 31 | #endif 32 | xpc_object_t xpc_bootstrap_pipe; 33 | }; 34 | 35 | struct _os_alloc_once_s { 36 | long once; 37 | void *ptr; 38 | }; 39 | extern struct _os_alloc_once_s _os_alloc_once_table[]; 40 | 41 | typedef struct _xpc_pipe_s* xpc_pipe_t; 42 | 43 | __attribute__((weak_import)) 44 | int xpc_pipe_routine(xpc_pipe_t pipe, xpc_object_t message, xpc_object_t *reply); 45 | int xpc_pipe_interface_routine(xpc_pipe_t pipe, int request, xpc_object_t message, xpc_object_t *reply, int unknown); 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /Src/launchdXPC/launchdXPC.m: -------------------------------------------------------------------------------- 1 | // 2 | // launchdXPC.m 3 | // Created by Patrick Wardle 4 | // Ported from code by Jonathan Levin 5 | // 6 | 7 | #include 8 | #import 9 | #import "launchdXPC.h" 10 | #import 11 | #import 12 | 13 | NSDictionary * infoForPID(pid_t pid) 14 | { 15 | NSDictionary *ret = nil; 16 | ProcessSerialNumber psn = { 0, 0 }; 17 | if (GetProcessForPID(pid, &psn) == noErr) { 18 | CFDictionaryRef cfDict = ProcessInformationCopyDictionary(&psn,kProcessDictionaryIncludeAllInformationMask); 19 | ret = [NSDictionary dictionaryWithDictionary:(__bridge NSDictionary *)cfDict]; 20 | CFRelease(cfDict); 21 | } 22 | return ret; 23 | } 24 | 25 | int getSubmittedPid(int pid) { 26 | NSDictionary* info = infoForPID(pid); 27 | //NSLog(@"info: %@", info); 28 | 29 | long long temp = [[info objectForKey:@"ParentPSN"] longLongValue]; 30 | long long hi = (temp >> 32) & 0x00000000FFFFFFFFLL; 31 | long long lo = (temp >> 0) & 0x00000000FFFFFFFFLL; 32 | ProcessSerialNumber parentPSN = {(unsigned long)hi, (unsigned long)lo}; 33 | 34 | //NSDictionary* parentDict = (__bridge NSDictionary*)ProcessInformationCopyDictionary (&parentPSN, kProcessDictionaryIncludeAllInformationMask); 35 | //NSLog(@"real parent info: %@", parentDict); 36 | //NSLog(@"real parent pid: %@", parentDict[@"pid"]); 37 | 38 | pid_t p = 0; 39 | GetProcessPID(&parentPSN, &p); 40 | //NSLog(@"real parent pid: %d", p); 41 | 42 | return p; 43 | } 44 | 45 | #define ROUTINE_DUMP_PROCESS 0x2c4 46 | 47 | //function definition 48 | NSMutableDictionary* parse(NSString* data); 49 | 50 | //hit up launchd (via XPC) to get process info 51 | NSString* getSubmittedByPlist(unsigned long pid) 52 | { 53 | //proc info 54 | NSDictionary* processInfo = nil; 55 | 56 | //xpc dictionary 57 | // passed to launchd to get proc info 58 | xpc_object_t procInfoRequest = NULL; 59 | 60 | //shared memory for XPC 61 | xpc_object_t sharedMemory = NULL; 62 | 63 | //xpc (out) dictionary 64 | // don't really contain response, but will have (any) errors 65 | xpc_object_t __autoreleasing response = NULL; 66 | 67 | //result 68 | int result = 0; 69 | 70 | //dylib handle 71 | void *handle = NULL; 72 | 73 | //function pointer to 'xpc_pipe_interface_routine' 74 | static int(*xpc_pipe_interface_routine_FP)(xpc_pipe_t, int, xpc_object_t, xpc_object_t*, int) = NULL; 75 | 76 | //(xpc) error 77 | int64_t xpcError = 0; 78 | 79 | //global data 80 | struct xpc_global_data* globalData = NULL; 81 | 82 | //init dictionary 83 | procInfoRequest = xpc_dictionary_create(NULL,NULL,0); 84 | 85 | //init buffer 86 | // size from reversing launchctl 87 | size_t processInfoLength = 0x100000; 88 | vm_address_t processInfoBuffer = 0; 89 | 90 | //bytes written via XPC call 91 | uint64_t bytesWritten = 0; 92 | 93 | //alloc buffer 94 | if(noErr != vm_allocate(mach_task_self(), &processInfoBuffer, processInfoLength, 0xf0000003)) 95 | { 96 | //bail 97 | goto bail; 98 | } 99 | 100 | sharedMemory = xpc_shmem_create((void*)processInfoBuffer, processInfoLength); 101 | if(0 == sharedMemory) 102 | { 103 | //bail 104 | goto bail; 105 | } 106 | 107 | //add pid to request 108 | xpc_dictionary_set_int64(procInfoRequest, "pid", pid); 109 | 110 | //add shared memory object 111 | xpc_dictionary_set_value(procInfoRequest, "shmem", sharedMemory); 112 | 113 | //grab from global data structure 114 | // contains XPC bootstrap pipe (launchd) 115 | globalData = (struct xpc_global_data *)_os_alloc_once_table[1].ptr; 116 | 117 | //open XPC lib 118 | handle = dlopen("/usr/lib/system/libxpc.dylib", RTLD_LAZY); 119 | if(NULL == handle) 120 | { 121 | //bail 122 | goto bail; 123 | } 124 | 125 | xpc_pipe_interface_routine_FP = dlsym(handle, "_xpc_pipe_interface_routine"); 126 | if(NULL == xpc_pipe_interface_routine_FP) 127 | { 128 | //bail 129 | goto bail; 130 | } 131 | 132 | //request process info 133 | result = xpc_pipe_interface_routine_FP((__bridge xpc_pipe_t)(globalData->xpc_bootstrap_pipe), ROUTINE_DUMP_PROCESS, procInfoRequest, &response, 0x0); 134 | if(0 != result) 135 | { 136 | //error 137 | goto bail; 138 | } 139 | 140 | //check for other error(s) 141 | xpcError = xpc_dictionary_get_int64(response, "error"); 142 | if(0 != xpcError) 143 | { 144 | //error 145 | //printf("error: %llx\n", xpcError); 146 | goto bail; 147 | } 148 | 149 | //get number of bytes written (to shared memory) 150 | bytesWritten = xpc_dictionary_get_uint64(response, "bytes-written"); 151 | 152 | //parse 153 | processInfo = parse([[NSString alloc] initWithBytes:(const void *)processInfoBuffer length:bytesWritten encoding:NSUTF8StringEncoding]); 154 | 155 | bail: 156 | 157 | //free buffer 158 | if(0 != processInfoBuffer) 159 | { 160 | //free 161 | vm_deallocate(mach_task_self(), processInfoBuffer, processInfoLength); 162 | processInfoBuffer = 0; 163 | } 164 | 165 | return processInfo[@"path"]; 166 | //return processInfo; 167 | } 168 | 169 | 170 | //parse proc info 171 | NSMutableDictionary* parse(NSString* data) 172 | { 173 | //parsed proc info 174 | NSMutableDictionary* procInfo = nil; 175 | 176 | //lines 177 | NSArray* lines = nil; 178 | 179 | //dictionaries 180 | NSMutableArray* dictionaries = nil; 181 | 182 | //alloc 183 | procInfo = [[NSMutableDictionary alloc] init]; 184 | 185 | //pool 186 | @autoreleasepool { 187 | 188 | //alloc 189 | dictionaries = [NSMutableArray array]; 190 | 191 | //split 192 | lines = [data componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; 193 | 194 | //start w/ top level 195 | [dictionaries addObject:procInfo]; 196 | 197 | //process 'dictionary' 198 | [lines enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 199 | 200 | //key 201 | NSString* key = nil; 202 | 203 | //tokens 204 | NSArray* tokens = nil; 205 | 206 | //obj should be a string 207 | if(YES != [obj isKindOfClass:[NSString class]]) return; 208 | 209 | //skip first line 210 | if(0 == idx) return; 211 | 212 | //trim object 213 | obj = [obj stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; 214 | 215 | //skip empty/blank lines 216 | if(0 == [obj length]) return; 217 | 218 | //key line? (line: "key = {") 219 | // extract key and add new dictionary 220 | if(YES == [obj hasSuffix:@"{"]) 221 | { 222 | //tokenize 223 | tokens = [obj componentsSeparatedByString:@"="]; 224 | 225 | //extract key 226 | // everything before '=' 227 | key = tokens.firstObject; 228 | if(0 == key.length) return; 229 | 230 | //init new dictionary 231 | dictionaries.lastObject[key] = [NSMutableDictionary dictionary]; 232 | 233 | //'save' new dictionary 234 | [dictionaries addObject:dictionaries.lastObject[key]]; 235 | 236 | return; 237 | } 238 | 239 | //end key line? (line: "}") 240 | // remove dictionary, as it's no longer needed 241 | if(YES == [obj isEqualToString:@"}"]) 242 | { 243 | //remove 244 | [dictionaries removeLastObject]; 245 | 246 | return; 247 | } 248 | 249 | //line w/ '=>' separator? 250 | // (line: "key => value") 251 | if(NSNotFound != [obj rangeOfString:@" => "].location) 252 | { 253 | //tokenize 254 | tokens = [obj componentsSeparatedByString:@" => "]; 255 | 256 | //key is first value 257 | key = tokens.firstObject; 258 | if(0 == key.length) return; 259 | 260 | //add key/value pair 261 | dictionaries.lastObject[key] = tokens.lastObject; 262 | 263 | return; 264 | } 265 | 266 | //line w/ '=' separator? 267 | // (line: "key = value") 268 | if(NSNotFound != [obj rangeOfString:@" = "].location) 269 | { 270 | //tokenize 271 | tokens = [obj componentsSeparatedByString:@" = "]; 272 | 273 | //key is first value 274 | key = tokens.firstObject; 275 | if(0 == key.length) return; 276 | 277 | //add key/value pair 278 | dictionaries.lastObject[key] = tokens.lastObject; 279 | 280 | return; 281 | } 282 | 283 | //non-key:value line in embedded dictionary? 284 | if( (dictionaries.lastObject != procInfo) && 285 | (NSNotFound == [obj rangeOfString:@" = "].location) ) 286 | { 287 | //add key/value pair 288 | dictionaries.lastObject[[NSNumber numberWithInteger:[dictionaries.lastObject count]]] = obj; 289 | 290 | return; 291 | } 292 | 293 | }]; 294 | 295 | } //pool 296 | 297 | return procInfo; 298 | } 299 | -------------------------------------------------------------------------------- /Src/launchdXPC/module.modulemap: -------------------------------------------------------------------------------- 1 | module LaunchdXPC [system] { 2 | header "launchdXPC.h" 3 | export * 4 | } 5 | 6 | -------------------------------------------------------------------------------- /Src/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // TrueTree 4 | // 5 | // Created by Jaron Bradley on 11/22/19. 6 | // 2020 TheMittenMac 7 | // 8 | 9 | import Foundation 10 | 11 | let version = 0.8 12 | 13 | // Go through command line arguments and set accordingly 14 | let argManager = ArgManager(suppliedArgs:CommandLine.arguments) 15 | 16 | 17 | // Must be root to gather all pid information 18 | if (NSUserName() != "root" && setuid(0) != 0) { 19 | print("This tool must be run as root in order to view all pid information") 20 | print("Exiting...") 21 | exit(1) 22 | } 23 | 24 | 25 | // Build all process objects 26 | let pc = ProcessCollector() 27 | 28 | // Get the launchd pid for future reference 29 | let rootNode = pc.getNodeForPid(1) 30 | guard rootNode != nil else { 31 | print("Could not find the launchd process. Aborting...") 32 | exit(1) 33 | } 34 | 35 | 36 | // If standard tree mode is active 37 | if argManager.standardMode { 38 | for proc in pc.processes { 39 | // Get the parent of the process 40 | let parentNode = pc.getNodeForPid(proc.ppid) 41 | 42 | // Assign this process as a child 43 | parentNode?.add(child: proc.node) 44 | } 45 | rootNode?.printTree() 46 | exit(0) 47 | } 48 | 49 | 50 | // If timeline mode is active 51 | if argManager.timelineMode == false { 52 | pc.printTimeline(outputFile: argManager.toFile) 53 | exit(0) 54 | } 55 | 56 | // Create a TrueTree 57 | for proc in pc.processes { 58 | 59 | // Create an on the fly node for a plist if one exists 60 | if let plist = proc.submittedByPlist { 61 | // Check if this plist is already in the tree 62 | if let existingPlistNode = rootNode?.searchPlist(value: plist) { 63 | existingPlistNode.add(child: proc.node) 64 | continue 65 | } 66 | 67 | let plistNode = Node(-1, path: plist, timestamp: "00:00:00", source: "launchd_xpc", displayString: plist) 68 | rootNode?.add(child: plistNode) 69 | plistNode.add(child: proc.node) 70 | continue 71 | } 72 | 73 | // Otherwise add the process as a child to its true parent 74 | let parentNode = pc.getNodeForPid(proc.trueParentPid) 75 | parentNode?.add(child: proc.node) 76 | 77 | // Create an on the fly node for any network connections this pid has and add them to itself 78 | if argManager.network { 79 | for x in proc.network { 80 | if let type = x.type { 81 | var displayString = "" 82 | if type == "TCP" { 83 | displayString = "\(type) - \(x.source):\(x.sourcePort) -> \(x.destination):\(x.destinationPort) - \(x.status)" 84 | } else { 85 | displayString = "\(type) - Local Port: \(x.sourcePort)" 86 | } 87 | 88 | let networkNode = Node(-1, path: "none", timestamp: "00:00:00", source: "Network", displayString: displayString) 89 | proc.node.add(child: networkNode) 90 | } 91 | } 92 | } 93 | } 94 | 95 | // print the launchd pid and all of it's children 96 | rootNode?.printTree() 97 | 98 | -------------------------------------------------------------------------------- /Src/network.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Network.swift 3 | // TrueTree 4 | // 5 | // Created by Jaron Bradley on 1/11/23. 6 | // Copyright © 2023 TheMittenMac. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ProcLib 11 | // Handy reference -> https://stackoverflow.com/questions/29294491/swift-obtaining-ip-address-from-socket-returns-weird-value 12 | 13 | struct NetworkConnection { 14 | let type: String? 15 | let pid: Int 16 | let family: String 17 | let source: String 18 | let sourcePort: UInt16 19 | let destination: String 20 | let destinationPort: UInt16 21 | let status: String 22 | } 23 | 24 | class NetworkConnections { 25 | private let PROC_PIDLISTFD_SIZE = Int32(MemoryLayout.stride) 26 | private let PROC_PIDFDSOCKETINFO_SIZE = Int32(MemoryLayout.stride) 27 | var connections = [NetworkConnection]() 28 | let pid: Int32 29 | 30 | init(pid: Int32) { 31 | self.pid = pid 32 | 33 | // get the size of the number of open files 34 | let size = proc_pidinfo(pid, PROC_PIDLISTFDS, 0, nil , 0) 35 | 36 | //get list of open file descriptors 37 | let fdInfo = UnsafeMutablePointer.allocate(capacity: Int(size)) 38 | defer { fdInfo.deallocate() } 39 | buildConnections(fdInfo, size) 40 | } 41 | 42 | private func getSocketFamily(socketInfoBuffer: UnsafeMutablePointer) -> String? { 43 | switch socketInfoBuffer.pointee.psi.soi_family { 44 | 45 | case AF_INET: 46 | return "IPv4" 47 | 48 | case AF_INET6: 49 | return "IPv6" 50 | 51 | default: 52 | return nil 53 | } 54 | } 55 | 56 | private func getType(socketInfoBuffer: UnsafeMutablePointer) -> String? { 57 | switch Int(socketInfoBuffer.pointee.psi.soi_kind) { 58 | case SOCKINFO_IN: 59 | return "UDP" 60 | 61 | case SOCKINFO_TCP: 62 | return "TCP" 63 | 64 | default: 65 | return nil 66 | } 67 | } 68 | 69 | private func getLocalPort(socketInfoBuffer: UnsafeMutablePointer, socketType: String) -> UInt16 { 70 | var port = UInt16(0) 71 | 72 | if socketType == "UDP" { 73 | port = UInt16(socketInfoBuffer.pointee.psi.soi_proto.pri_in.insi_lport) 74 | } 75 | 76 | if socketType == "TCP" { 77 | port = UInt16(socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_lport) 78 | } 79 | 80 | return port.byteSwapped 81 | } 82 | 83 | private func getRemotePort(socketInfoBuffer: UnsafeMutablePointer, socketType: String) -> UInt16 { 84 | if socketType == "UDP" { 85 | return 0 86 | } 87 | 88 | let port = UInt16(socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport) 89 | return port.byteSwapped 90 | } 91 | 92 | private func getIP4DestinationAddress(socketInfoBuffer: UnsafeMutablePointer) -> String { 93 | var result = [CChar].init(repeating: 0, count: 16) 94 | inet_ntop(AF_INET, &socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr.ina_46.i46a_addr4, &result, 16) 95 | let ipAddr = String(cString: result) 96 | 97 | return ipAddr 98 | } 99 | 100 | private func getIP6DestinationAddress(socketInfoBuffer: UnsafeMutablePointer) -> String { 101 | var result = [CChar].init(repeating: 0, count: 128) 102 | inet_ntop(AF_INET6, &(socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_faddr.ina_6), &result, 128); 103 | let ipAddr = String(cString: result) 104 | 105 | return ipAddr 106 | } 107 | 108 | private func getIP4SourceAddress(socketInfoBuffer: UnsafeMutablePointer) -> String { 109 | var result = [CChar].init(repeating: 0, count: 16) 110 | inet_ntop(AF_INET, &socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_laddr.ina_46.i46a_addr4, &result, 16) 111 | let ipAddr = String(cString: result) 112 | 113 | return ipAddr 114 | } 115 | 116 | private func getIP6SourceAddress(socketInfoBuffer: UnsafeMutablePointer) -> String { 117 | var result = [CChar].init(repeating: 0, count: 128) 118 | inet_ntop(AF_INET6, &(socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_laddr.ina_6), &result, 128); 119 | let ipAddr = String(cString: result) 120 | 121 | return ipAddr 122 | } 123 | 124 | private func getStatus(socketInfoBuffer: UnsafeMutablePointer) -> String { 125 | var status = "" 126 | switch socketInfoBuffer.pointee.psi.soi_proto.pri_tcp.tcpsi_state { 127 | case TSI_S_CLOSED: 128 | status = "CLOSED" 129 | case TSI_S_LISTEN: 130 | status = "LISTENING" 131 | case TSI_S_SYN_SENT: 132 | status = "SYN SENT (active, have sent syn)" 133 | case TSI_S_SYN_RECEIVED: 134 | status = "SYN RECEIVED (have send and received syn)" 135 | case TSI_S_ESTABLISHED: 136 | status = "ESTABLISHED" 137 | case TSI_S__CLOSE_WAIT: 138 | status = "CLOSE WAIT (received fin, waiting for close) " 139 | case TSI_S_FIN_WAIT_1: 140 | status = "FIN WAIT1 (have closed, sent fin)" 141 | case TSI_S_CLOSING: 142 | status = "CLOSING (closed xchd FIN; await FIN ACK)" 143 | case TSI_S_LAST_ACK: 144 | status = "LAST ACK (had fin and close; await FIN ACK)" 145 | case TSI_S_FIN_WAIT_2: 146 | status = "FIN WAIT2 (have closed, fin is acked)" 147 | case TSI_S_TIME_WAIT: 148 | status = "TIME WAIT (in 2*msl quiet wait after close)" 149 | case TSI_S_RESERVED: 150 | status = "RESERVED" 151 | default: 152 | status = "Unknown" 153 | } 154 | 155 | return status 156 | } 157 | 158 | private func buildConnections(_ fdInfo: UnsafeMutablePointer, _ size: Int32) { 159 | proc_pidinfo(self.pid, PROC_PIDLISTFDS, 0, fdInfo, size) 160 | 161 | // Go through each open file descriptor 162 | for x in 0...Int(size/PROC_PIDLISTFD_SIZE) { 163 | var localPort = UInt16(0) 164 | var destinationPort = UInt16(0) 165 | var destination = "" 166 | var source = "" 167 | 168 | // Skip if file descriptor is not a socket 169 | if PROX_FDTYPE_SOCKET != fdInfo[x].proc_fdtype { continue } 170 | 171 | // Get the socket info, skipping if an error occurs 172 | let socketInfo = UnsafeMutablePointer.allocate(capacity: 1) 173 | defer { socketInfo.deallocate() } 174 | 175 | if PROC_PIDFDSOCKETINFO_SIZE != proc_pidfdinfo(self.pid, fdInfo[x].proc_fd, PROC_PIDFDSOCKETINFO, socketInfo, PROC_PIDFDSOCKETINFO_SIZE) { 176 | continue 177 | } 178 | 179 | // Get IPv4 or IPV6 180 | guard let family = getSocketFamily(socketInfoBuffer: socketInfo) else { return } 181 | 182 | // Get UDP or TCP 183 | guard let type = getType(socketInfoBuffer: socketInfo) else { return } 184 | 185 | // If this is a UDP connection 186 | if type == "UDP" { 187 | localPort = getLocalPort(socketInfoBuffer: socketInfo, socketType: type) 188 | 189 | } else if type == "TCP" { 190 | // Far more details can be collected from TCP connections 191 | localPort = getLocalPort(socketInfoBuffer: socketInfo, socketType: type) 192 | destinationPort = UInt16(socketInfo.pointee.psi.soi_proto.pri_tcp.tcpsi_ini.insi_fport).byteSwapped 193 | 194 | // If this is a IPv4 address get the local and remote connections 195 | if family == "IPv4" { 196 | destination = getIP4DestinationAddress(socketInfoBuffer: socketInfo) 197 | source = getIP4SourceAddress(socketInfoBuffer: socketInfo) 198 | 199 | } else if family == "IPv6" { 200 | destination = getIP6DestinationAddress(socketInfoBuffer: socketInfo) 201 | source = getIP6SourceAddress(socketInfoBuffer: socketInfo) 202 | } 203 | } 204 | 205 | let status = getStatus(socketInfoBuffer: socketInfo) 206 | 207 | let n = NetworkConnection(type: type, 208 | pid: Int(pid), 209 | family: family, 210 | source: source, 211 | sourcePort: localPort, 212 | destination: destination, 213 | destinationPort: destinationPort, 214 | status: status) 215 | 216 | connections.append(n) 217 | } 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /Src/node.swift: -------------------------------------------------------------------------------- 1 | // 2 | // tree.swift 3 | // TrueTree 4 | // 5 | // Created by Jaron Bradley on 11/2/19. 6 | // 2020 TheMittenMac 7 | // 8 | 9 | 10 | import Foundation 11 | 12 | 13 | final class Node { 14 | let pid: Int 15 | let path: String 16 | let timestamp: String 17 | let source: String 18 | let displayString: String 19 | private(set) var children: [Node] 20 | 21 | var fileName: String { 22 | if let fileName = path.components(separatedBy: "/").last { 23 | return fileName 24 | } 25 | 26 | return path 27 | } 28 | 29 | init(_ pid: Int, path: String, timestamp: String, source: String, displayString: String) { 30 | self.pid = pid 31 | self.path = path 32 | self.timestamp = timestamp 33 | self.source = source 34 | self.displayString = displayString 35 | children = [] 36 | } 37 | 38 | func add(child: Node) { 39 | children.append(child) 40 | } 41 | 42 | func searchPlist(value: String) -> Node? { 43 | if value == self.path { 44 | return self 45 | } 46 | 47 | for child in children { 48 | if let found = child.searchPlist(value: value) { 49 | return found 50 | } 51 | } 52 | return nil 53 | } 54 | } 55 | 56 | 57 | // Extension for printing the tree. Great approach. Currently uses global vars from argmanager 58 | //https://stackoverflow.com/questions/46371513/printing-a-tree-with-indents-swift 59 | extension Node { 60 | func treeLines(_ nodeIndent:String="", _ childIndent:String="") -> [String] { 61 | var text = "" 62 | if path.hasSuffix(".plist") { 63 | if argManager.color { text = "\(displayString.blue)" } else { text = "\(displayString)" } 64 | 65 | } else if displayString.hasPrefix("UDP") || displayString.hasPrefix("TCP") { 66 | if argManager.color { text = "\(displayString.yellow)" } else { text = "\(displayString)" } 67 | 68 | } else { 69 | if argManager.color { 70 | if argManager.showpath { 71 | text = "\(displayString)" 72 | } else { 73 | text = "\(fileName)" 74 | } 75 | if argManager.showpid { text += " \(String(pid).magenta)"} 76 | if argManager.timestamps { text += " \(timestamp.cyan)"} 77 | if argManager.sources { text += " Aquired parent from -> \(source)".red } 78 | } else { 79 | text = "\(displayString) \(String(pid))" 80 | if argManager.timestamps { text += " \(timestamp)"} 81 | if argManager.sources { text += " Aquired parent from -> \(source)" } 82 | } 83 | 84 | } 85 | 86 | return [ nodeIndent + text ] 87 | + children.enumerated().map{ ($0 < children.count-1, $1) } 88 | .flatMap{ $0 ? $1.treeLines("┣╸","┃ ") : $1.treeLines("┗╸"," ") } 89 | .map{ childIndent + $0 } 90 | 91 | } 92 | 93 | func printTree() { 94 | let tree = treeLines().joined(separator:"\n") 95 | 96 | if let toFile = argManager.toFile { 97 | let fileUrl = URL(fileURLWithPath: toFile) 98 | do { 99 | try tree.write(to: fileUrl, atomically: true, encoding: String.Encoding.utf8) 100 | } catch { 101 | print("Could not write TrueTree output to specified file") 102 | } 103 | } else { 104 | print(tree) 105 | } 106 | } 107 | } 108 | 109 | -------------------------------------------------------------------------------- /Src/process.swift: -------------------------------------------------------------------------------- 1 | // 2 | // process.swift 3 | // TrueTree 4 | // 5 | // Created by Jaron Bradley on 11/1/19. 6 | // 2020 TheMittenMac 7 | // 8 | 9 | import Foundation 10 | import ProcLib 11 | import LaunchdXPC 12 | 13 | struct Process { 14 | let pid: Int 15 | let ppid: Int 16 | let responsiblePid: Int 17 | let path: String 18 | let submittedByPid: Int? 19 | let submittedByPlist: String? 20 | let timestamp: String 21 | let node: Node 22 | let trueParentPid: Int 23 | let source: String 24 | let network: [NetworkConnection] 25 | } 26 | 27 | 28 | class ProcessCollector { 29 | var processes = [Process]() 30 | let timestampFormat = DateFormatter() 31 | let InfoSize = Int32(MemoryLayout.stride) 32 | let MaxPathLen = Int(4 * MAXPATHLEN) 33 | typealias rpidFunc = @convention(c) (CInt) -> CInt 34 | 35 | init() { 36 | timestampFormat.dateFormat = "yyyy-MM-dd HH:mm:ss" 37 | self.collect() 38 | } 39 | 40 | func collect() { 41 | // Inspired by https://gist.github.com/kainjow/0e7650cc797a52261e0f4ba851477c2f 42 | 43 | // Call proc_listallpids once with nil/0 args to get the current number of pids 44 | let initialNumPids = proc_listallpids(nil, 0) 45 | 46 | // Allocate a buffer of these number of pids. 47 | // Make sure to deallocate it as this class does not manage memory for us. 48 | let buffer = UnsafeMutablePointer.allocate(capacity: Int(initialNumPids)) 49 | defer { 50 | buffer.deallocate() 51 | } 52 | 53 | // Calculate the buffer's total length in bytes 54 | let bufferLength = initialNumPids * Int32(MemoryLayout.size) 55 | 56 | // Call the function again with our inputs now ready 57 | let numPids = proc_listallpids(buffer, bufferLength) 58 | 59 | // Loop through each pid and build a process struct 60 | for i in 0.. 1 { 91 | trueParent = submittedPid 92 | source = "application_services" 93 | } else if responsiblePid != pid { 94 | trueParent = responsiblePid 95 | source = "responsible_pid" 96 | } else { 97 | trueParent = ppid 98 | source = "parent_process" 99 | } 100 | 101 | // Collect a plist if it caused this program to run 102 | var plistNode: String? 103 | if let launchctlPlist = getSubmittedByPlist(UInt(pid)) { 104 | if launchctlPlist.hasSuffix(".plist") { 105 | plistNode = launchctlPlist 106 | source = "launchd_xpc" 107 | } 108 | } 109 | 110 | // Collect network connections 111 | let n = NetworkConnections(pid: Int32(pid)) 112 | let networkConnections = n.connections 113 | 114 | // Create the tree node 115 | let node = Node(pid, path: path, timestamp: ts, source: source, displayString: path) 116 | 117 | // Create the process entry 118 | let p = Process(pid: pid, 119 | ppid: ppid, 120 | responsiblePid: responsiblePid, 121 | path: path, 122 | submittedByPid: submittedPid, 123 | submittedByPlist: plistNode ?? nil, 124 | timestamp: ts, 125 | node: node, 126 | trueParentPid: trueParent, 127 | source: source, 128 | network: networkConnections 129 | ) 130 | 131 | // Add the process to the array of captured processes 132 | processes.append(p) 133 | 134 | } 135 | 136 | // Sort the processes by time 137 | processes = processes.sorted { $0.timestamp < $1.timestamp } 138 | } 139 | 140 | func getPPID(_ pidOfInterest:Int) -> Int? { 141 | // Call proc_pidinfo and return nil on error 142 | let pidInfo = UnsafeMutablePointer.allocate(capacity: 1) 143 | guard InfoSize == proc_pidinfo(Int32(pidOfInterest), PROC_PIDTBSDINFO, 0, pidInfo, InfoSize) else { return nil } 144 | defer { pidInfo.deallocate() } 145 | 146 | return Int(pidInfo.pointee.pbi_ppid) 147 | } 148 | 149 | func getResponsiblePid(_ pidOfInterest:Int) -> Int { 150 | // Get responsible pid using private Apple API 151 | let rpidSym:UnsafeMutableRawPointer! = dlsym(UnsafeMutableRawPointer(bitPattern: -1), "responsibility_get_pid_responsible_for_pid") 152 | let responsiblePid = unsafeBitCast(rpidSym, to: rpidFunc.self)(CInt(pidOfInterest)) 153 | 154 | guard responsiblePid != -1 else { 155 | print("Error getting responsible pid for process \(pidOfInterest). Setting to responsible pid to itself") 156 | return pidOfInterest 157 | } 158 | 159 | return Int(responsiblePid) 160 | } 161 | 162 | func getPath(_ pidOfInterest: Int) -> String { 163 | let pathBuffer = UnsafeMutablePointer.allocate(capacity: MaxPathLen) 164 | defer { pathBuffer.deallocate() } 165 | pathBuffer.initialize(repeating: 0, count: MaxPathLen) 166 | 167 | if proc_pidpath(Int32(pidOfInterest), pathBuffer, UInt32(MemoryLayout.stride * MaxPathLen)) == 0 { 168 | return "unknown" 169 | } 170 | 171 | return String(cString: pathBuffer) 172 | } 173 | 174 | func getTimestamp(_ pidOfInterest: Int) -> Date { 175 | // Call proc_pidinfo and return current date on error 176 | let pidInfo = UnsafeMutablePointer.allocate(capacity: 1) 177 | guard InfoSize == proc_pidinfo(Int32(pidOfInterest), PROC_PIDTBSDINFO, 0, pidInfo, InfoSize) else { return Date() } 178 | defer { pidInfo.deallocate() } 179 | 180 | return Date(timeIntervalSince1970: TimeInterval(pidInfo.pointee.pbi_start_tvsec)) 181 | } 182 | 183 | func getNodeForPid(_ pidOfInterest: Int) -> Node? { 184 | for proc in processes { 185 | if proc.pid == pidOfInterest { 186 | return proc.node 187 | } 188 | } 189 | 190 | return nil 191 | } 192 | } 193 | 194 | extension ProcessCollector { 195 | func printTimeline(outputFile:String?) { 196 | for proc in processes { 197 | let text = "\(proc.timestamp) \(proc.path) \(proc.pid)" 198 | if let outputFile = outputFile { 199 | let fileUrl = URL(fileURLWithPath: outputFile) 200 | do { 201 | try text.write(to: fileUrl, atomically: true, encoding: String.Encoding.utf8) 202 | } catch { 203 | print("Could not write TrueTree output to specified file") 204 | } 205 | } 206 | print("\(proc.timestamp) \(proc.path) \(proc.pid)") 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /TrueTree.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8A78B5F72388924B00DA165C /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A78B5F62388924B00DA165C /* main.swift */; }; 11 | 8A78B6002388928900DA165C /* launchdXPC.m in Sources */ = {isa = PBXBuildFile; fileRef = 8A78B5FF2388928900DA165C /* launchdXPC.m */; }; 12 | 8A78B604238C1FD800DA165C /* node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A78B603238C1FD800DA165C /* node.swift */; }; 13 | 8A78B606238C200E00DA165C /* process.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8A78B605238C200E00DA165C /* process.swift */; }; 14 | 8AFE066523FF1D9A0032DA23 /* args.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8AFE066423FF1D9A0032DA23 /* args.swift */; }; 15 | A300C394296FBC8B008FF5F0 /* network.swift in Sources */ = {isa = PBXBuildFile; fileRef = A300C393296FBC8B008FF5F0 /* network.swift */; }; 16 | A3315741293A6F640075C2C2 /* colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3315740293A6F640075C2C2 /* colors.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 8A78B5F12388924B00DA165C /* CopyFiles */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = /usr/share/man/man1/; 24 | dstSubfolderSpec = 0; 25 | files = ( 26 | ); 27 | runOnlyForDeploymentPostprocessing = 1; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 8A78B5F32388924B00DA165C /* TrueTree */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = TrueTree; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | 8A78B5F62388924B00DA165C /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; 34 | 8A78B5FE2388928900DA165C /* launchdXPC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = launchdXPC.h; sourceTree = ""; }; 35 | 8A78B5FF2388928900DA165C /* launchdXPC.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = launchdXPC.m; sourceTree = ""; }; 36 | 8A78B6022389E49200DA165C /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 37 | 8A78B603238C1FD800DA165C /* node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = node.swift; sourceTree = ""; }; 38 | 8A78B605238C200E00DA165C /* process.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = process.swift; sourceTree = ""; }; 39 | 8AB67275239568980040C141 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; 40 | 8AB67276239568A70040C141 /* ProcLib.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ProcLib.h; sourceTree = ""; }; 41 | 8AE4947F2410244F00E35A32 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 42 | 8AFE066423FF1D9A0032DA23 /* args.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = args.swift; sourceTree = ""; }; 43 | A300C393296FBC8B008FF5F0 /* network.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = network.swift; sourceTree = ""; }; 44 | A3315740293A6F640075C2C2 /* colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = colors.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 8A78B5F02388924B00DA165C /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 8A78B5EA2388924B00DA165C = { 59 | isa = PBXGroup; 60 | children = ( 61 | 8AE4947F2410244F00E35A32 /* README.md */, 62 | 8A78B5F52388924B00DA165C /* Src */, 63 | 8A78B5F42388924B00DA165C /* Products */, 64 | ); 65 | sourceTree = ""; 66 | }; 67 | 8A78B5F42388924B00DA165C /* Products */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 8A78B5F32388924B00DA165C /* TrueTree */, 71 | ); 72 | name = Products; 73 | sourceTree = ""; 74 | }; 75 | 8A78B5F52388924B00DA165C /* Src */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 8AB672742395684F0040C141 /* ProcLib */, 79 | 8A78B5FD2388927500DA165C /* launchdXPC */, 80 | 8AFE066423FF1D9A0032DA23 /* args.swift */, 81 | 8A78B5F62388924B00DA165C /* main.swift */, 82 | 8A78B605238C200E00DA165C /* process.swift */, 83 | 8A78B603238C1FD800DA165C /* node.swift */, 84 | A3315740293A6F640075C2C2 /* colors.swift */, 85 | A300C393296FBC8B008FF5F0 /* network.swift */, 86 | ); 87 | path = Src; 88 | sourceTree = ""; 89 | }; 90 | 8A78B5FD2388927500DA165C /* launchdXPC */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 8A78B6022389E49200DA165C /* module.modulemap */, 94 | 8A78B5FE2388928900DA165C /* launchdXPC.h */, 95 | 8A78B5FF2388928900DA165C /* launchdXPC.m */, 96 | ); 97 | path = launchdXPC; 98 | sourceTree = ""; 99 | }; 100 | 8AB672742395684F0040C141 /* ProcLib */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 8AB67275239568980040C141 /* module.modulemap */, 104 | 8AB67276239568A70040C141 /* ProcLib.h */, 105 | ); 106 | path = ProcLib; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 8A78B5F22388924B00DA165C /* TrueTree */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 8A78B5FA2388924B00DA165C /* Build configuration list for PBXNativeTarget "TrueTree" */; 115 | buildPhases = ( 116 | 8A78B5EF2388924B00DA165C /* Sources */, 117 | 8A78B5F02388924B00DA165C /* Frameworks */, 118 | 8A78B5F12388924B00DA165C /* CopyFiles */, 119 | ); 120 | buildRules = ( 121 | ); 122 | dependencies = ( 123 | ); 124 | name = TrueTree; 125 | productName = TrueTree; 126 | productReference = 8A78B5F32388924B00DA165C /* TrueTree */; 127 | productType = "com.apple.product-type.tool"; 128 | }; 129 | /* End PBXNativeTarget section */ 130 | 131 | /* Begin PBXProject section */ 132 | 8A78B5EB2388924B00DA165C /* Project object */ = { 133 | isa = PBXProject; 134 | attributes = { 135 | LastSwiftUpdateCheck = 1100; 136 | LastUpgradeCheck = 1240; 137 | ORGANIZATIONNAME = mittenmac; 138 | TargetAttributes = { 139 | 8A78B5F22388924B00DA165C = { 140 | CreatedOnToolsVersion = 11.0; 141 | LastSwiftMigration = 1100; 142 | }; 143 | }; 144 | }; 145 | buildConfigurationList = 8A78B5EE2388924B00DA165C /* Build configuration list for PBXProject "TrueTree" */; 146 | compatibilityVersion = "Xcode 9.3"; 147 | developmentRegion = en; 148 | hasScannedForEncodings = 0; 149 | knownRegions = ( 150 | en, 151 | Base, 152 | ); 153 | mainGroup = 8A78B5EA2388924B00DA165C; 154 | productRefGroup = 8A78B5F42388924B00DA165C /* Products */; 155 | projectDirPath = ""; 156 | projectRoot = ""; 157 | targets = ( 158 | 8A78B5F22388924B00DA165C /* TrueTree */, 159 | ); 160 | }; 161 | /* End PBXProject section */ 162 | 163 | /* Begin PBXSourcesBuildPhase section */ 164 | 8A78B5EF2388924B00DA165C /* Sources */ = { 165 | isa = PBXSourcesBuildPhase; 166 | buildActionMask = 2147483647; 167 | files = ( 168 | 8A78B5F72388924B00DA165C /* main.swift in Sources */, 169 | 8AFE066523FF1D9A0032DA23 /* args.swift in Sources */, 170 | A3315741293A6F640075C2C2 /* colors.swift in Sources */, 171 | 8A78B6002388928900DA165C /* launchdXPC.m in Sources */, 172 | 8A78B604238C1FD800DA165C /* node.swift in Sources */, 173 | A300C394296FBC8B008FF5F0 /* network.swift in Sources */, 174 | 8A78B606238C200E00DA165C /* process.swift in Sources */, 175 | ); 176 | runOnlyForDeploymentPostprocessing = 0; 177 | }; 178 | /* End PBXSourcesBuildPhase section */ 179 | 180 | /* Begin XCBuildConfiguration section */ 181 | 8A78B5F82388924B00DA165C /* Debug */ = { 182 | isa = XCBuildConfiguration; 183 | buildSettings = { 184 | ALWAYS_SEARCH_USER_PATHS = NO; 185 | CLANG_ANALYZER_NONNULL = YES; 186 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 187 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 188 | CLANG_CXX_LIBRARY = "libc++"; 189 | CLANG_ENABLE_MODULES = YES; 190 | CLANG_ENABLE_OBJC_ARC = YES; 191 | CLANG_ENABLE_OBJC_WEAK = YES; 192 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 193 | CLANG_WARN_BOOL_CONVERSION = YES; 194 | CLANG_WARN_COMMA = YES; 195 | CLANG_WARN_CONSTANT_CONVERSION = YES; 196 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 197 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 198 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 199 | CLANG_WARN_EMPTY_BODY = YES; 200 | CLANG_WARN_ENUM_CONVERSION = YES; 201 | CLANG_WARN_INFINITE_RECURSION = YES; 202 | CLANG_WARN_INT_CONVERSION = YES; 203 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 204 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 205 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 206 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 207 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 208 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 209 | CLANG_WARN_STRICT_PROTOTYPES = YES; 210 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 211 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 212 | CLANG_WARN_UNREACHABLE_CODE = YES; 213 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 214 | COPY_PHASE_STRIP = NO; 215 | DEBUG_INFORMATION_FORMAT = dwarf; 216 | ENABLE_STRICT_OBJC_MSGSEND = YES; 217 | ENABLE_TESTABILITY = YES; 218 | GCC_C_LANGUAGE_STANDARD = gnu11; 219 | GCC_DYNAMIC_NO_PIC = NO; 220 | GCC_NO_COMMON_BLOCKS = YES; 221 | GCC_OPTIMIZATION_LEVEL = 0; 222 | GCC_PREPROCESSOR_DEFINITIONS = ( 223 | "DEBUG=1", 224 | "$(inherited)", 225 | ); 226 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 227 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 228 | GCC_WARN_UNDECLARED_SELECTOR = YES; 229 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 230 | GCC_WARN_UNUSED_FUNCTION = YES; 231 | GCC_WARN_UNUSED_VARIABLE = YES; 232 | MACOSX_DEPLOYMENT_TARGET = 10.15; 233 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 234 | MTL_FAST_MATH = YES; 235 | ONLY_ACTIVE_ARCH = NO; 236 | SDKROOT = macosx; 237 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 238 | SWIFT_INCLUDE_PATHS = ""; 239 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 240 | }; 241 | name = Debug; 242 | }; 243 | 8A78B5F92388924B00DA165C /* Release */ = { 244 | isa = XCBuildConfiguration; 245 | buildSettings = { 246 | ALWAYS_SEARCH_USER_PATHS = NO; 247 | CLANG_ANALYZER_NONNULL = YES; 248 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 249 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 250 | CLANG_CXX_LIBRARY = "libc++"; 251 | CLANG_ENABLE_MODULES = YES; 252 | CLANG_ENABLE_OBJC_ARC = YES; 253 | CLANG_ENABLE_OBJC_WEAK = YES; 254 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 255 | CLANG_WARN_BOOL_CONVERSION = YES; 256 | CLANG_WARN_COMMA = YES; 257 | CLANG_WARN_CONSTANT_CONVERSION = YES; 258 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 259 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 260 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 261 | CLANG_WARN_EMPTY_BODY = YES; 262 | CLANG_WARN_ENUM_CONVERSION = YES; 263 | CLANG_WARN_INFINITE_RECURSION = YES; 264 | CLANG_WARN_INT_CONVERSION = YES; 265 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 266 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 267 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 268 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 269 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 270 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 271 | CLANG_WARN_STRICT_PROTOTYPES = YES; 272 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 273 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 274 | CLANG_WARN_UNREACHABLE_CODE = YES; 275 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 276 | COPY_PHASE_STRIP = NO; 277 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 278 | ENABLE_NS_ASSERTIONS = NO; 279 | ENABLE_STRICT_OBJC_MSGSEND = YES; 280 | GCC_C_LANGUAGE_STANDARD = gnu11; 281 | GCC_NO_COMMON_BLOCKS = YES; 282 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 284 | GCC_WARN_UNDECLARED_SELECTOR = YES; 285 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 286 | GCC_WARN_UNUSED_FUNCTION = YES; 287 | GCC_WARN_UNUSED_VARIABLE = YES; 288 | MACOSX_DEPLOYMENT_TARGET = 10.15; 289 | MTL_ENABLE_DEBUG_INFO = NO; 290 | MTL_FAST_MATH = YES; 291 | ONLY_ACTIVE_ARCH = NO; 292 | SDKROOT = macosx; 293 | SWIFT_COMPILATION_MODE = wholemodule; 294 | SWIFT_INCLUDE_PATHS = ""; 295 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 296 | }; 297 | name = Release; 298 | }; 299 | 8A78B5FB2388924B00DA165C /* Debug */ = { 300 | isa = XCBuildConfiguration; 301 | buildSettings = { 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_USE_OPTIMIZATION_PROFILE = NO; 304 | CODE_SIGN_IDENTITY = "Apple Development"; 305 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 306 | CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; 307 | CODE_SIGN_STYLE = Manual; 308 | DEVELOPMENT_TEAM = ""; 309 | "DEVELOPMENT_TEAM[sdk=macosx*]" = ""; 310 | ENABLE_HARDENED_RUNTIME = YES; 311 | INSTALL_PATH = /usr/local/bin/; 312 | MACOSX_DEPLOYMENT_TARGET = 10.13; 313 | "OTHER_CODE_SIGN_FLAGS[sdk=*]" = "--timestamp"; 314 | PRODUCT_BUNDLE_IDENTIFIER = truetree; 315 | PRODUCT_NAME = "$(TARGET_NAME)"; 316 | PROVISIONING_PROFILE_SPECIFIER = ""; 317 | SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Src/launchdXPC $(SRCROOT) $(SRCROOT)/Src/ProcLib"; 318 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; 319 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 320 | SWIFT_VERSION = 5.0; 321 | USER_HEADER_SEARCH_PATHS = "$(SRCROOT)"; 322 | }; 323 | name = Debug; 324 | }; 325 | 8A78B5FC2388924B00DA165C /* Release */ = { 326 | isa = XCBuildConfiguration; 327 | buildSettings = { 328 | CLANG_ENABLE_MODULES = YES; 329 | CLANG_USE_OPTIMIZATION_PROFILE = NO; 330 | CODE_SIGN_IDENTITY = "Apple Development"; 331 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; 332 | CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; 333 | CODE_SIGN_STYLE = Manual; 334 | DEVELOPMENT_TEAM = ""; 335 | "DEVELOPMENT_TEAM[sdk=macosx*]" = ""; 336 | ENABLE_HARDENED_RUNTIME = YES; 337 | INSTALL_PATH = /usr/local/bin/; 338 | MACOSX_DEPLOYMENT_TARGET = 10.13; 339 | ONLY_ACTIVE_ARCH = NO; 340 | PRODUCT_BUNDLE_IDENTIFIER = truetree; 341 | PRODUCT_NAME = "$(TARGET_NAME)"; 342 | PROVISIONING_PROFILE_SPECIFIER = ""; 343 | SWIFT_INCLUDE_PATHS = "$(SRCROOT)/Src/launchdXPC $(SRCROOT) $(SRCROOT)/Src/ProcLib"; 344 | SWIFT_OBJC_INTERFACE_HEADER_NAME = "$(SWIFT_MODULE_NAME)-Swift.h"; 345 | SWIFT_VERSION = 5.0; 346 | USER_HEADER_SEARCH_PATHS = "$(SRCROOT)"; 347 | }; 348 | name = Release; 349 | }; 350 | /* End XCBuildConfiguration section */ 351 | 352 | /* Begin XCConfigurationList section */ 353 | 8A78B5EE2388924B00DA165C /* Build configuration list for PBXProject "TrueTree" */ = { 354 | isa = XCConfigurationList; 355 | buildConfigurations = ( 356 | 8A78B5F82388924B00DA165C /* Debug */, 357 | 8A78B5F92388924B00DA165C /* Release */, 358 | ); 359 | defaultConfigurationIsVisible = 0; 360 | defaultConfigurationName = Release; 361 | }; 362 | 8A78B5FA2388924B00DA165C /* Build configuration list for PBXNativeTarget "TrueTree" */ = { 363 | isa = XCConfigurationList; 364 | buildConfigurations = ( 365 | 8A78B5FB2388924B00DA165C /* Debug */, 366 | 8A78B5FC2388924B00DA165C /* Release */, 367 | ); 368 | defaultConfigurationIsVisible = 0; 369 | defaultConfigurationName = Release; 370 | }; 371 | /* End XCConfigurationList section */ 372 | }; 373 | rootObject = 8A78B5EB2388924B00DA165C /* Project object */; 374 | } 375 | -------------------------------------------------------------------------------- /TrueTree.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /TrueTree.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License. 2 | --------------------------------------------------------------------------------