private static Options ParseImpl(string[] args) { BlockingMq Mq = BlockingMq.GetMq(); Mq.Info("Parsing args..."); Options parsedConfig = new Options(); // define args ValueArgument <string> configFileArg = new ValueArgument <string>('z', "config", "Path to a .toml config file. Run with \'generate\' to puke a sample config file into the working directory."); ValueArgument <string> outFileArg = new ValueArgument <string>('o', "outfile", "Path for output file. You probably want this if you're not using -s."); ValueArgument <string> verboseArg = new ValueArgument <string>('v', "verbosity", "Controls verbosity level, options are Trace (most verbose), Debug (less verbose), Info (less verbose still, default), and Data (results only). e.g '-v debug' "); SwitchArgument helpArg = new SwitchArgument('h', "help", "Displays this help.", false); SwitchArgument stdOutArg = new SwitchArgument('s', "stdout", "Enables outputting results to stdout as soon as they're found. You probably want this if you're not using -o.", false); ValueArgument <string> snaffleArg = new ValueArgument <string>('m', "snaffle", "Enables and assigns an output dir for Snaffler to automatically snaffle a copy of any found files."); ValueArgument <long> snaffleSizeArg = new ValueArgument <long>('l', "snafflesize", "Maximum size of file to snaffle, in bytes. Defaults to 10MB."); //var fileHuntArg = new SwitchArgument('f', "filehuntoff", // "Disables file discovery, will only perform computer and share discovery.", false); ValueArgument <string> dirTargetArg = new ValueArgument <string>('i', "dirtarget", "Disables computer and share discovery, requires a path to a directory in which to perform file discovery."); ValueArgument <string> domainArg = new ValueArgument <string>('d', "domain", "Domain to search for computers to search for shares on to search for files in. Easy."); ValueArgument <string> domainControllerArg = new ValueArgument <string>('c', "domaincontroller", "Domain controller to query for a list of domain computers."); ValueArgument <long> maxGrepSizeArg = new ValueArgument <long>('r', "maxgrepsize", "The maximum size file (in bytes) to search inside for interesting strings. Defaults to 500k."); ValueArgument <int> grepContextArg = new ValueArgument <int>('j', "grepcontext", "How many bytes of context either side of found strings in files to show, e.g. -j 200"); SwitchArgument domainUserArg = new SwitchArgument('u', "domainusers", "Makes Snaffler grab a list of interesting-looking accounts from the domain and uses them in searches.", false); // list of letters i haven't used yet: abefgknpqwxy CommandLineParser.CommandLineParser parser = new CommandLineParser.CommandLineParser(); parser.Arguments.Add(configFileArg); parser.Arguments.Add(outFileArg); parser.Arguments.Add(helpArg); parser.Arguments.Add(stdOutArg); parser.Arguments.Add(snaffleArg); parser.Arguments.Add(snaffleSizeArg); parser.Arguments.Add(dirTargetArg); parser.Arguments.Add(domainArg); parser.Arguments.Add(verboseArg); parser.Arguments.Add(domainControllerArg); parser.Arguments.Add(maxGrepSizeArg); parser.Arguments.Add(grepContextArg); parser.Arguments.Add(domainUserArg); // extra check to handle builtin behaviour from cmd line arg parser if ((args.Contains("--help") || args.Contains("/?") || args.Contains("help") || args.Contains("-h") || args.Length == 0)) { parser.ShowUsage(); Environment.Exit(0); } TomlSettings settings = TomlSettings.Create(cfg => cfg .ConfigureType <LogLevel>(tc => tc.WithConversionFor <TomlString>(conv => conv .FromToml(s => (LogLevel)Enum.Parse(typeof(LogLevel), s.Value, ignoreCase: true)) .ToToml(e => e.ToString())))); try { parser.ParseCommandLine(args); if (configFileArg.Parsed) { if (!configFileArg.Value.Equals("generate")) { string configFile = configFileArg.Value; parsedConfig = Toml.ReadFile <Options>(configFile, settings); parsedConfig.PrepareClassifiers(); Mq.Info("Read config file from " + configFile); return(parsedConfig); } } if (parsedConfig.ClassifierRules.Count <= 0) { parsedConfig.BuildDefaultClassifiers(); } // get the args into our config // output args if (outFileArg.Parsed && (!String.IsNullOrEmpty(outFileArg.Value))) { parsedConfig.LogToFile = true; parsedConfig.LogFilePath = outFileArg.Value; Mq.Degub("Logging to file at " + parsedConfig.LogFilePath); } // Set loglevel. if (verboseArg.Parsed) { parsedConfig.LogLevelString = verboseArg.Value; Mq.Degub("Requested verbosity level: " + parsedConfig.LogLevelString); } // if enabled, display findings to the console parsedConfig.LogToConsole = stdOutArg.Parsed; Mq.Degub("Enabled logging to stdout."); // args that tell us about targeting if ((domainArg.Parsed) && (!String.IsNullOrEmpty(domainArg.Value))) { parsedConfig.TargetDomain = domainArg.Value; Mq.Degub("Target domain is " + domainArg.Value); } if ((domainControllerArg.Parsed) && (!String.IsNullOrEmpty(domainControllerArg.Value))) { parsedConfig.TargetDc = domainControllerArg.Value; Mq.Degub("Target DC is " + domainControllerArg.Value); } if (domainUserArg.Parsed) { parsedConfig.DomainUserRules = true; Mq.Degub("Enabled use of domain user accounts in rules."); } if (dirTargetArg.Parsed) { parsedConfig.ShareFinderEnabled = false; parsedConfig.PathTargets = new string[] { dirTargetArg.Value }; Mq.Degub("Disabled finding shares."); Mq.Degub("Target path is " + dirTargetArg.Value); } if (maxGrepSizeArg.Parsed) { parsedConfig.MaxSizeToGrep = maxGrepSizeArg.Value; Mq.Degub("We won't bother looking inside files if they're bigger than " + parsedConfig.MaxSizeToGrep + " bytes"); } if (snaffleArg.Parsed) { parsedConfig.SnafflePath = snaffleArg.Value; } if (snaffleSizeArg.Parsed) { parsedConfig.MaxSizeToSnaffle = snaffleSizeArg.Value; } // how many bytes if (grepContextArg.Parsed) { parsedConfig.MatchContextBytes = grepContextArg.Value; Mq.Degub( "We'll show you " + grepContextArg.Value + " bytes of context around matches inside files."); } // if enabled, grab a copy of files that we like. if (snaffleArg.Parsed) { if (snaffleArg.Value.Length <= 0) { Mq.Error("-m or -mirror arg requires a path value."); throw new ArgumentException("Invalid argument combination."); } parsedConfig.Snaffle = true; parsedConfig.SnafflePath = snaffleArg.Value.TrimEnd('\\'); Mq.Degub("Mirroring matched files to path " + parsedConfig.SnafflePath); } if (!parsedConfig.LogToConsole && !parsedConfig.LogToFile) { Mq.Error( "\nYou didn't enable output to file or to the console so you won't see any results or debugs or anything. Your l0ss."); throw new ArgumentException("Pointless argument combination."); } if (configFileArg.Parsed) { if (configFileArg.Value.Equals("generate")) { Toml.WriteFile(parsedConfig, ".\\default.toml", settings); Mq.Info("Wrote default config values to .\\default.toml"); Mq.Terminate(); } } parsedConfig.PrepareClassifiers(); } catch (Exception e) { Mq.Error(e.ToString()); throw; } return(parsedConfig); }
private static Options ParseImpl(string[] args) { BlockingMq Mq = BlockingMq.GetMq(); Mq.Info("Parsing args..."); Options parsedConfig = new Options(); // define args ValueArgument <string> configFileArg = new ValueArgument <string>('z', "config", "Path to a .toml config file. Run with \'generate\' to puke a sample config file into the working directory."); ValueArgument <string> outFileArg = new ValueArgument <string>('o', "outfile", "Path for output file. You probably want this if you're not using -s."); ValueArgument <string> verboseArg = new ValueArgument <string>('v', "verbosity", "Controls verbosity level, options are Trace (most verbose), Debug (less verbose), Info (less verbose still, default), and Data (results only). e.g '-v debug' "); SwitchArgument helpArg = new SwitchArgument('h', "help", "Displays this help.", false); SwitchArgument stdOutArg = new SwitchArgument('s', "stdout", "Enables outputting results to stdout as soon as they're found. You probably want this if you're not using -o.", false); ValueArgument <int> interestLevel = new ValueArgument <int>('b', "interest", "Interest level to report (0-3)"); ValueArgument <string> snaffleArg = new ValueArgument <string>('m', "snaffle", "Enables and assigns an output dir for Snaffler to automatically snaffle a copy of any found files."); ValueArgument <long> snaffleSizeArg = new ValueArgument <long>('l', "snafflesize", "Maximum size of file to snaffle, in bytes. Defaults to 10MB."); //var fileHuntArg = new SwitchArgument('f', "filehuntoff", // "Disables file discovery, will only perform computer and share discovery.", false); ValueArgument <string> dirTargetArg = new ValueArgument <string>('i', "dirtarget", "Disables computer and share discovery, requires a path to a directory in which to perform file discovery."); ValueArgument <string> domainArg = new ValueArgument <string>('d', "domain", "Domain to search for computers to search for shares on to search for files in. Easy."); ValueArgument <string> domainControllerArg = new ValueArgument <string>('c', "domaincontroller", "Domain controller to query for a list of domain computers."); ValueArgument <long> maxGrepSizeArg = new ValueArgument <long>('r', "maxgrepsize", "The maximum size file (in bytes) to search inside for interesting strings. Defaults to 500k."); ValueArgument <int> grepContextArg = new ValueArgument <int>('j', "grepcontext", "How many bytes of context either side of found strings in files to show, e.g. -j 200"); SwitchArgument domainUserArg = new SwitchArgument('u', "domainusers", "Makes Snaffler grab a list of interesting-looking accounts from the domain and uses them in searches.", false); ValueArgument <int> maxThreadsArg = new ValueArgument <int>('x', "maxthreads", "How many threads to be snaffling with. Any less than 4 and you're gonna have a bad time."); SwitchArgument tsvArg = new SwitchArgument('y', "tsv", "Makes Snaffler output as tsv.", false); SwitchArgument dfsArg = new SwitchArgument('f', "dfs", "Limits Snaffler to finding file shares via DFS, for \"OPSEC\" reasons.", false); SwitchArgument findSharesOnlyArg = new SwitchArgument('a', "sharesonly", "Stops after finding shares, doesn't walk their filesystems.", false); ValueArgument <string> compTargetArg = new ValueArgument <string>('n', "comptarget", "Computer (or comma separated list) to target."); ValueArgument <string> ruleDirArg = new ValueArgument <string>('p', "rulespath", "Path to a directory full of toml-formatted rules. Snaffler will load all of these in place of the default ruleset."); ValueArgument <string> logType = new ValueArgument <string>('t', "logtype", "Type of log you would like to output. Currently supported options are plain and JSON. Defaults to plain."); // list of letters i haven't used yet: egknqw CommandLineParser.CommandLineParser parser = new CommandLineParser.CommandLineParser(); parser.Arguments.Add(configFileArg); parser.Arguments.Add(outFileArg); parser.Arguments.Add(helpArg); parser.Arguments.Add(stdOutArg); parser.Arguments.Add(snaffleArg); parser.Arguments.Add(snaffleSizeArg); parser.Arguments.Add(dirTargetArg); parser.Arguments.Add(interestLevel); parser.Arguments.Add(domainArg); parser.Arguments.Add(verboseArg); parser.Arguments.Add(domainControllerArg); parser.Arguments.Add(maxGrepSizeArg); parser.Arguments.Add(grepContextArg); parser.Arguments.Add(domainUserArg); parser.Arguments.Add(tsvArg); parser.Arguments.Add(dfsArg); parser.Arguments.Add(findSharesOnlyArg); parser.Arguments.Add(maxThreadsArg); parser.Arguments.Add(compTargetArg); parser.Arguments.Add(ruleDirArg); parser.Arguments.Add(logType); // extra check to handle builtin behaviour from cmd line arg parser if ((args.Contains("--help") || args.Contains("/?") || args.Contains("help") || args.Contains("-h") || args.Length == 0)) { parser.ShowUsage(); Environment.Exit(0); } TomlSettings settings = TomlSettings.Create(cfg => cfg .ConfigureType <LogLevel>(tc => tc.WithConversionFor <TomlString>(conv => conv .FromToml(s => (LogLevel)Enum.Parse(typeof(LogLevel), s.Value, ignoreCase: true)) .ToToml(e => e.ToString())))); try { parser.ParseCommandLine(args); if (logType.Parsed && !String.IsNullOrWhiteSpace(logType.Value)) { //Set the default to plain parsedConfig.LogType = LogType.Plain; //if they set a different type then replace it with the new type. if (logType.Value.ToLower() == "json") { parsedConfig.LogType = LogType.JSON; } else { Mq.Info("Invalid type argument passed (" + logType.Value + ") defaulting to plaintext"); } } if (ruleDirArg.Parsed && !String.IsNullOrWhiteSpace(ruleDirArg.Value)) { parsedConfig.RuleDir = ruleDirArg.Value; } // get the args into our config // output args if (outFileArg.Parsed && (!String.IsNullOrEmpty(outFileArg.Value))) { parsedConfig.LogToFile = true; parsedConfig.LogFilePath = outFileArg.Value; Mq.Degub("Logging to file at " + parsedConfig.LogFilePath); } if (dfsArg.Parsed) { parsedConfig.DfsOnly = dfsArg.Value; } if (compTargetArg.Parsed) { string[] compTargets = null; if (compTargetArg.Value.Contains(",")) { compTargets = compTargetArg.Value.Split(','); } else { compTargets = new string[] { compTargetArg.Value }; } parsedConfig.ComputerTargets = compTargets; } if (findSharesOnlyArg.Parsed) { parsedConfig.ScanFoundShares = false; } if (maxThreadsArg.Parsed) { parsedConfig.MaxThreads = maxThreadsArg.Value; } parsedConfig.ShareThreads = parsedConfig.MaxThreads / 3; parsedConfig.FileThreads = parsedConfig.MaxThreads / 3; parsedConfig.TreeThreads = parsedConfig.MaxThreads / 3; if (tsvArg.Parsed) { parsedConfig.LogTSV = true; if (parsedConfig.Separator == ' ') { parsedConfig.Separator = '\t'; } } // Set loglevel. if (verboseArg.Parsed) { parsedConfig.LogLevelString = verboseArg.Value; Mq.Degub("Requested verbosity level: " + parsedConfig.LogLevelString); } // if enabled, display findings to the console parsedConfig.LogToConsole = stdOutArg.Parsed; Mq.Degub("Enabled logging to stdout."); // args that tell us about targeting if ((domainArg.Parsed) && (!String.IsNullOrEmpty(domainArg.Value))) { parsedConfig.TargetDomain = domainArg.Value; Mq.Degub("Target domain is " + domainArg.Value); } if ((domainControllerArg.Parsed) && (!String.IsNullOrEmpty(domainControllerArg.Value))) { parsedConfig.TargetDc = domainControllerArg.Value; Mq.Degub("Target DC is " + domainControllerArg.Value); } if (domainUserArg.Parsed) { parsedConfig.DomainUserRules = true; Mq.Degub("Enabled use of domain user accounts in rules."); } if (dirTargetArg.Parsed) { parsedConfig.ShareFinderEnabled = false; parsedConfig.PathTargets.Add(dirTargetArg.Value); Mq.Degub("Disabled finding shares."); Mq.Degub("Target path is " + dirTargetArg.Value); } if (maxGrepSizeArg.Parsed) { parsedConfig.MaxSizeToGrep = maxGrepSizeArg.Value; Mq.Degub("We won't bother looking inside files if they're bigger than " + parsedConfig.MaxSizeToGrep + " bytes"); } if (snaffleSizeArg.Parsed) { parsedConfig.MaxSizeToSnaffle = snaffleSizeArg.Value; } if (interestLevel.Parsed) { parsedConfig.InterestLevel = interestLevel.Value; Mq.Degub("Requested interest level: " + parsedConfig.InterestLevel); } // how many bytes if (grepContextArg.Parsed) { parsedConfig.MatchContextBytes = grepContextArg.Value; Mq.Degub( "We'll show you " + grepContextArg.Value + " bytes of context around matches inside files."); } // if enabled, grab a copy of files that we like. if (snaffleArg.Parsed) { if (snaffleArg.Value.Length <= 0) { Mq.Error("-m or -mirror arg requires a path value."); throw new ArgumentException("Invalid argument combination."); } parsedConfig.Snaffle = true; parsedConfig.SnafflePath = snaffleArg.Value.TrimEnd('\\'); Mq.Degub("Mirroring matched files to path " + parsedConfig.SnafflePath); } if (configFileArg.Parsed) { if (configFileArg.Value.Equals("generate")) { Toml.WriteFile(parsedConfig, ".\\default.toml", settings); Console.WriteLine("Wrote config values to .\\default.toml"); parsedConfig.LogToConsole = true; Mq.Degub("Enabled logging to stdout."); Environment.Exit(0); } else { string configFile = configFileArg.Value; parsedConfig = Toml.ReadFile <Options>(configFile, settings); Mq.Info("Read config file from " + configFile); } } if (!parsedConfig.LogToConsole && !parsedConfig.LogToFile) { Mq.Error( "\nYou didn't enable output to file or to the console so you won't see any results or debugs or anything. Your l0ss."); throw new ArgumentException("Pointless argument combination."); } if (parsedConfig.ClassifierRules.Count <= 0) { if (String.IsNullOrWhiteSpace(parsedConfig.RuleDir)) { // get all the embedded toml file resources string[] resourceNames = Assembly.GetExecutingAssembly().GetManifestResourceNames(); StringBuilder sb = new StringBuilder(); foreach (string resourceName in resourceNames) { if (!resourceName.EndsWith(".toml")) { // skip this one as it's just metadata continue; } string ruleFile = ReadResource(resourceName); sb.AppendLine(ruleFile); } string bulktoml = sb.ToString(); // deserialise the toml to an actual ruleset RuleSet ruleSet = Toml.ReadString <RuleSet>(bulktoml, settings); // stick the rules in our config! parsedConfig.ClassifierRules = ruleSet.ClassifierRules; } else { string[] tomlfiles = Directory.GetFiles(parsedConfig.RuleDir, "*.toml", SearchOption.AllDirectories); StringBuilder sb = new StringBuilder(); foreach (string tomlfile in tomlfiles) { string tomlstring = File.ReadAllText(tomlfile); sb.AppendLine(tomlstring); } string bulktoml = sb.ToString(); // deserialise the toml to an actual ruleset RuleSet ruleSet = Toml.ReadString <RuleSet>(bulktoml, settings); // stick the rules in our config! parsedConfig.ClassifierRules = ruleSet.ClassifierRules; } } parsedConfig.PrepareClassifiers(); } catch (Exception e) { Mq.Error(e.ToString()); throw; } return(parsedConfig); }