Beispiel #1
0
        public DirResult ClassifyDir(string dir)
        {
            BlockingMq Mq        = BlockingMq.GetMq();
            DirResult  dirResult = new DirResult()
            {
                DirPath = dir,
                Triage  = ClassifierRule.Triage,
                ScanDir = true,
            };
            // check if it matches
            TextClassifier textClassifier = new TextClassifier(ClassifierRule);
            TextResult     textResult     = textClassifier.TextMatch(dir);

            if (textResult != null)
            {
                // if it does, see what we're gonna do with it
                switch (ClassifierRule.MatchAction)
                {
                case MatchAction.Discard:
                    dirResult.ScanDir = false;
                    return(dirResult);

                case MatchAction.Snaffle:
                    dirResult.Triage = ClassifierRule.Triage;
                    Mq.DirResult(dirResult);
                    return(dirResult);

                default:
                    Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
                    return(null);
                }
            }
            return(dirResult);
        }
Beispiel #2
0
        public bool ClassifyShare(string share)
        {
            BlockingMq Mq = BlockingMq.GetMq();

            // first time we hit sysvol, toggle the flag and keep going. every other time, bail out.
            if (share.ToLower().EndsWith("sysvol"))
            {
                if (MyOptions.ScanSysvol == false)
                {
                    return(true);
                }
                MyOptions.ScanSysvol = false;
            }
            ;
            // same for netlogon
            if (share.ToLower().EndsWith("netlogon"))
            {
                if (MyOptions.ScanNetlogon == false)
                {
                    return(true);
                }
                MyOptions.ScanNetlogon = false;
            }
            // check if it matches
            TextClassifier textClassifier = new TextClassifier(ClassifierRule);
            TextResult     textResult     = textClassifier.TextMatch(share);

            if (textResult != null)
            {
                // if it does, see what we're gonna do with it
                switch (ClassifierRule.MatchAction)
                {
                case MatchAction.Discard:
                    return(true);

                case MatchAction.Snaffle:
                    // in this context snaffle means 'send a report up the queue but don't scan the share'
                    if (IsShareReadable(share))
                    {
                        ShareResult shareResult = new ShareResult()
                        {
                            Triage    = ClassifierRule.Triage,
                            Listable  = true,
                            SharePath = share
                        };
                        Mq.ShareResult(shareResult);
                    }
                    return(true);

                default:
                    Mq.Error("You've got a misconfigured share ClassifierRule named " + ClassifierRule.RuleName + ".");
                    return(false);
                }
            }
            return(false);
        }
Beispiel #3
0
        public X509Certificate2 parseCert(string certPath, string password = null)
        {
            BlockingMq Mq = BlockingMq.GetMq();
            // IT TURNS OUT THAT new X509Certificate2() actually writes a file to a temp path and if you
            // don't manage it yourself it hits 65,000 temp files and hangs.

            var tempfile = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());

            File.Copy(certPath, tempfile);
            X509Certificate2 parsedCert = null;

            try
            {
                if (Path.GetExtension(certPath) == ".pem")
                {
                    string pemstring  = File.ReadAllText(tempfile);
                    byte[] certBuffer = Helpers.GetBytesFromPEM(pemstring, PemStringType.Certificate);
                    byte[] keyBuffer  = Helpers.GetBytesFromPEM(pemstring, PemStringType.RsaPrivateKey);

                    if (certBuffer != null)
                    {
                        parsedCert = new X509Certificate2(certBuffer);
                        if (keyBuffer != null)
                        {
                            RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
                            parsedCert.PrivateKey = prov;
                        }
                    }
                    else
                    {
                        Mq.Error("Failure parsing " + certPath);
                    }
                }
                else
                {
                    parsedCert = new X509Certificate2(tempfile, password);
                }
            }
            catch (Exception e)
            {
                File.Delete(tempfile);
                throw e;
            }

            File.Delete(tempfile);

            return(parsedCert);
        }
Beispiel #4
0
        public static RwStatus CanRw(FileInfo fileInfo)
        {
            BlockingMq Mq = BlockingMq.GetMq();

            try
            {
                RwStatus rwStatus = new RwStatus {
                    CanWrite = CanIWrite(fileInfo), CanRead = CanIRead(fileInfo)
                };
                return(rwStatus);
            }
            catch (Exception e)
            {
                Mq.Error(e.ToString());
                return(new RwStatus {
                    CanWrite = false, CanRead = false
                });;
            }
        }
Beispiel #5
0
        public static Options Parse(string[] args)
        {
            BlockingMq Mq = BlockingMq.GetMq();
            Options    options;

            // parse the args
            try
            {
                options = ParseImpl(args);
                if (options == null)
                {
                    throw new ArgumentException("Unable to correctly parse arguments.");
                }
            }
            catch
            {
                Mq.Error("Something went wrong parsing args.");
                throw;
            }

            Mq.Info("Parsed args successfully.");
            return(options);
        }
Beispiel #6
0
        public bool ClassifyShare(string share)
        {
            BlockingMq Mq = BlockingMq.GetMq();

            // check if the share has a matching classifier
            TextClassifier textClassifier = new TextClassifier(ClassifierRule);
            TextResult     textResult     = textClassifier.TextMatch(share);

            if (textResult != null)
            {
                // if it does, see what we're gonna do with it
                switch (ClassifierRule.MatchAction)
                {
                case MatchAction.Discard:
                    return(true);

                case MatchAction.Snaffle:
                    // in this context snaffle means 'send a report up the queue but don't scan the share'
                    if (IsShareReadable(share))
                    {
                        ShareResult shareResult = new ShareResult()
                        {
                            Triage    = ClassifierRule.Triage,
                            Listable  = true,
                            SharePath = share
                        };
                        Mq.ShareResult(shareResult);
                    }
                    return(true);

                default:
                    Mq.Error("You've got a misconfigured share ClassifierRule named " + ClassifierRule.RuleName + ".");
                    return(false);
                }
            }
            return(false);
        }
Beispiel #7
0
        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);
        }
Beispiel #8
0
        public bool ClassifyFile(FileInfo fileInfo)
        {
            BlockingMq Mq = BlockingMq.GetMq();
            // figure out what part we gonna look at
            string stringToMatch = null;

            switch (ClassifierRule.MatchLocation)
            {
            case MatchLoc.FileExtension:
                stringToMatch = fileInfo.Extension;
                // special handling to treat files named like 'thing.kdbx.bak'
                if (stringToMatch == ".bak")
                {
                    // strip off .bak
                    string subName = fileInfo.Name.Replace(".bak", "");
                    stringToMatch = Path.GetExtension(subName);
                    // if this results in no file extension, put it back.
                    if (stringToMatch == "")
                    {
                        stringToMatch = ".bak";
                    }
                }
                // this is insane that i have to do this but apparently files with no extension return
                // this bullshit
                if (stringToMatch == "")
                {
                    return(false);
                }
                break;

            case MatchLoc.FileName:
                stringToMatch = fileInfo.Name;
                break;

            case MatchLoc.FilePath:
                stringToMatch = fileInfo.FullName;
                break;

            case MatchLoc.FileLength:
                if (!SizeMatch(fileInfo))
                {
                    return(false);
                }
                else
                {
                    break;
                }

            default:
                Mq.Error("You've got a misconfigured file classifier rule named " + ClassifierRule.RuleName + ".");
                return(false);
            }

            TextResult textResult = null;

            if (!String.IsNullOrEmpty(stringToMatch))
            {
                TextClassifier textClassifier = new TextClassifier(ClassifierRule);
                // check if it matches
                textResult = textClassifier.TextMatch(stringToMatch);
                if (textResult == null)
                {
                    // if it doesn't we just bail now.
                    return(false);
                }
            }

            FileResult fileResult;

            // if it matches, see what we're gonna do with it
            switch (ClassifierRule.MatchAction)
            {
            case MatchAction.Discard:
                // chuck it.
                return(true);

            case MatchAction.Snaffle:
                // snaffle that bad boy
                fileResult = new FileResult(fileInfo)
                {
                    MatchedRule = ClassifierRule,
                    TextResult  = textResult
                };
                // if the file was list-only, don't bother sending a result back to the user.
                if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite)
                {
                    return(false);
                }
                ;
                Mq.FileResult(fileResult);
                return(false);

            case MatchAction.CheckForKeys:
                // do a special x509 dance
                List <string> x509MatchReason = x509Match(fileInfo);
                if (x509MatchReason.Count >= 0)
                {
                    // if there were any matchreasons, cat them together...
                    string matchContext = String.Join(",", x509MatchReason);
                    // and sling the results on the queue
                    fileResult = new FileResult(fileInfo)
                    {
                        MatchedRule = ClassifierRule,
                        TextResult  = new TextResult()
                        {
                            MatchContext   = matchContext,
                            MatchedStrings = new List <string>()
                            {
                                ""
                            }
                        }
                    };

                    if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite)
                    {
                        return(false);
                    }
                    ;

                    Mq.FileResult(fileResult);
                }
                return(false);

            case MatchAction.Relay:
                // bounce it on to the next ClassifierRule
                try
                {
                    bool fLoggedContentSizeWarning = false;

                    foreach (string relayTarget in ClassifierRule.RelayTargets)
                    {
                        ClassifierRule nextRule =
                            MyOptions.ClassifierRules.First(thing => thing.RuleName == relayTarget);

                        if (nextRule.EnumerationScope == EnumerationScope.ContentsEnumeration)
                        {
                            if (fileInfo.Length > MyOptions.MaxSizeToGrep)
                            {
                                if (!fLoggedContentSizeWarning)
                                {
                                    // Just log once per relay rule, no need to fill up the log with one for each relay target
                                    Mq.Trace("The following file was bigger than the MaxSizeToGrep config parameter:" + fileInfo.FullName);
                                    fLoggedContentSizeWarning = true;
                                }

                                continue;
                            }

                            ContentClassifier nextContentClassifier = new ContentClassifier(nextRule);
                            nextContentClassifier.ClassifyContent(fileInfo);
                        }
                        else if (nextRule.EnumerationScope == EnumerationScope.FileEnumeration)
                        {
                            FileClassifier nextFileClassifier = new FileClassifier(nextRule);
                            nextFileClassifier.ClassifyFile(fileInfo);
                        }
                        else
                        {
                            Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
                        }
                    }
                    return(false);
                }
                catch (IOException e)
                {
                    Mq.Trace(e.ToString());
                }
                catch (Exception e)
                {
                    Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
                    Mq.Trace(e.ToString());
                }
                return(false);

            case MatchAction.EnterArchive:
                // do a special looking inside archive files dance using
                // https://github.com/adamhathcock/sharpcompress
                // TODO FUUUUUCK
                throw new NotImplementedException("Haven't implemented walking dir structures inside archives.");

            default:
                Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
                return(false);
            }
        }
Beispiel #9
0
        public List <string> x509Match(FileInfo fileInfo)
        {
            BlockingMq       Mq           = BlockingMq.GetMq();
            string           certPath     = fileInfo.FullName;
            List <string>    matchReasons = new List <string>();
            X509Certificate2 parsedCert   = null;
            bool             nopwrequired = false;

            // TODO - handle if there is no private key, strip out unnecessary stuff from Certificate.cs, make work with pfx style stuff below

            try
            {
                // try to parse it, it'll throw if it needs a password
                parsedCert = parseCert(certPath);

                // if it parses we know we didn't need a password
                nopwrequired = true;
            }
            catch (CryptographicException e)
            {
                // if it doesn't parse that almost certainly means we need a password
                Mq.Trace(e.ToString());

                // build the list of passwords to try including the filename
                List <string> passwords = MyOptions.CertPasswords;
                passwords.Add(Path.GetFileNameWithoutExtension(fileInfo.Name));

                // try each of our very obvious passwords
                foreach (string password in MyOptions.CertPasswords)
                {
                    try
                    {
                        parsedCert = parseCert(certPath, password);
                        if (password == "")
                        {
                            matchReasons.Add("PasswordBlank");
                        }
                        else
                        {
                            matchReasons.Add("PasswordCracked: " + password);
                        }
                    }
                    catch (CryptographicException ee)
                    {
                        Mq.Trace("Password " + password + " invalid for cert file " + fileInfo.FullName + " " + ee.ToString());
                    }
                }
                if (matchReasons.Count == 0)
                {
                    matchReasons.Add("HasPassword");
                    matchReasons.Add("LookNearbyFor.txtFiles");
                }
            }
            catch (Exception e)
            {
                Mq.Error("Unhandled exception parsing cert: " + fileInfo.FullName + " " + e.ToString());
            }

            if (parsedCert != null)
            {
                // check if it includes a private key, if not, who cares?
                if (parsedCert.HasPrivateKey)
                {
                    matchReasons.Add("HasPrivateKey");

                    if (nopwrequired)
                    {
                        matchReasons.Add("NoPasswordRequired");
                    }

                    matchReasons.Add("Subject:" + parsedCert.Subject);

                    // take a look at the extensions
                    X509ExtensionCollection extensions = parsedCert.Extensions;

                    // this feels dumb but whatever
                    foreach (X509Extension extension in extensions)
                    {
                        AsnEncodedData asndata       = new AsnEncodedData(extension.Oid, extension.RawData);
                        string         asndataString = asndata.Format(false);
                        if (extension.Oid.FriendlyName == "Basic Constraints")
                        {
                            if (asndataString.Contains("Subject Type=CA"))
                            {
                                matchReasons.Add("IsCACert");
                            }
                        }
                        if (extension.GetType() == typeof(X509KeyUsageExtension))
                        {
                            matchReasons.Add((extension as X509KeyUsageExtension).KeyUsages.ToString());
                        }
                        if (extension.GetType() == typeof(X509EnhancedKeyUsageExtension))
                        {
                            List <string> ekus = new List <string>();

                            X509EnhancedKeyUsageExtension ekuExtension = (X509EnhancedKeyUsageExtension)extension;
                            foreach (Oid eku in ekuExtension.EnhancedKeyUsages)
                            {
                                ekus.Add(eku.FriendlyName);
                            }
                            // include the EKUs in the info we're passing to the user
                            string ekustring = String.Join("|", ekus);
                            matchReasons.Add(ekustring);
                        }
                        ;
                        if (extension.Oid.FriendlyName == "Subject Alternative Name")
                        {
                            byte[] sanbytes = extension.RawData;
                            string san      = Encoding.UTF8.GetString(sanbytes, 0, sanbytes.Length);
                            matchReasons.Add(asndataString);
                        }
                    }

                    matchReasons.Add("Expiry:" + parsedCert.GetExpirationDateString());
                    matchReasons.Add("Issuer:" + parsedCert.Issuer);
                }
            }
            return(matchReasons);
        }
Beispiel #10
0
        public bool ClassifyFile(FileInfo fileInfo)
        {
            BlockingMq Mq = BlockingMq.GetMq();
            // figure out what part we gonna look at
            string stringToMatch = null;

            switch (ClassifierRule.MatchLocation)
            {
            case MatchLoc.FileExtension:
                stringToMatch = fileInfo.Extension;
                // special handling to treat files named like 'thing.kdbx.bak'
                if (stringToMatch == ".bak")
                {
                    // strip off .bak
                    string subName = fileInfo.Name.Replace(".bak", "");
                    stringToMatch = Path.GetExtension(subName);
                    // if this results in no file extension, put it back.
                    if (stringToMatch == "")
                    {
                        stringToMatch = ".bak";
                    }
                }
                // this is insane that i have to do this but apparently files with no extension return
                // this bullshit
                if (stringToMatch == "")
                {
                    return(false);
                }
                break;

            case MatchLoc.FileName:
                stringToMatch = fileInfo.Name;
                break;

            case MatchLoc.FilePath:
                stringToMatch = fileInfo.FullName;
                break;

            case MatchLoc.FileLength:
                if (!SizeMatch(fileInfo))
                {
                    return(false);
                }
                else
                {
                    break;
                }

            default:
                Mq.Error("You've got a misconfigured file classifier rule named " + ClassifierRule.RuleName + ".");
                return(false);
            }

            TextResult textResult = null;

            if (!String.IsNullOrEmpty(stringToMatch))
            {
                TextClassifier textClassifier = new TextClassifier(ClassifierRule);
                // check if it matches
                textResult = textClassifier.TextMatch(stringToMatch);
                if (textResult == null)
                {
                    // if it doesn't we just bail now.
                    return(false);
                }
            }

            FileResult fileResult;

            // if it matches, see what we're gonna do with it
            switch (ClassifierRule.MatchAction)
            {
            case MatchAction.Discard:
                // chuck it.
                return(true);

            case MatchAction.Snaffle:
                // snaffle that bad boy
                fileResult = new FileResult(fileInfo)
                {
                    MatchedRule = ClassifierRule,
                    TextResult  = textResult
                };
                Mq.FileResult(fileResult);
                return(true);

            case MatchAction.CheckForKeys:
                // do a special x509 dance
                if (x509PrivKeyMatch(fileInfo))
                {
                    fileResult = new FileResult(fileInfo)
                    {
                        MatchedRule = ClassifierRule
                    };
                    Mq.FileResult(fileResult);
                }
                return(true);

            case MatchAction.Relay:
                // bounce it on to the next ClassifierRule
                // TODO concurrency uplift make this a new task on the poolq
                try
                {
                    ClassifierRule nextRule =
                        MyOptions.ClassifierRules.First(thing => thing.RuleName == ClassifierRule.RelayTarget);

                    if (nextRule.EnumerationScope == EnumerationScope.ContentsEnumeration)
                    {
                        ContentClassifier nextContentClassifier = new ContentClassifier(nextRule);
                        nextContentClassifier.ClassifyContent(fileInfo);
                        return(true);
                    }
                    else if (nextRule.EnumerationScope == EnumerationScope.FileEnumeration)
                    {
                        FileClassifier nextFileClassifier = new FileClassifier(nextRule);
                        nextFileClassifier.ClassifyFile(fileInfo);
                        return(true);
                    }
                    else
                    {
                        Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
                        return(false);
                    }
                }
                catch (IOException e)
                {
                    Mq.Trace(e.ToString());
                }
                catch (Exception e)
                {
                    Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
                    Mq.Trace(e.ToString());
                }
                return(false);

            case MatchAction.EnterArchive:
                // do a special looking inside archive files dance using
                // https://github.com/adamhathcock/sharpcompress
                // TODO FUUUUUCK
                throw new NotImplementedException("Haven't implemented walking dir structures inside archives. Prob needs pool queue.");

            default:
                Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
                return(false);
            }
        }
        public void ClassifyContent(FileInfo fileInfo)
        {
            BlockingMq Mq = BlockingMq.GetMq();
            FileResult fileResult;

            try
            {
                if (MyOptions.MaxSizeToGrep >= fileInfo.Length)
                {
                    // figure out if we need to look at the content as bytes or as string.
                    switch (ClassifierRule.MatchLocation)
                    {
                    case MatchLoc.FileContentAsBytes:
                        byte[] fileBytes = File.ReadAllBytes(fileInfo.FullName);
                        if (ByteMatch(fileBytes))
                        {
                            fileResult = new FileResult(fileInfo)
                            {
                                MatchedRule = ClassifierRule
                            };
                            Mq.FileResult(fileResult);
                        }
                        return;

                    case MatchLoc.FileContentAsString:
                        string fileString = File.ReadAllText(fileInfo.FullName);

                        TextClassifier textClassifier = new TextClassifier(ClassifierRule);
                        TextResult     textResult     = textClassifier.TextMatch(fileString);
                        if (textResult != null)
                        {
                            fileResult = new FileResult(fileInfo)
                            {
                                MatchedRule = ClassifierRule,
                                TextResult  = textResult
                            };
                            Mq.FileResult(fileResult);
                        }
                        return;

                    case MatchLoc.FileLength:
                        bool lengthResult = SizeMatch(fileInfo);
                        if (lengthResult)
                        {
                            fileResult = new FileResult(fileInfo)
                            {
                                MatchedRule = ClassifierRule
                            };
                            Mq.FileResult(fileResult);
                        }
                        return;

                    case MatchLoc.FileMD5:
                        bool Md5Result = MD5Match(fileInfo);
                        if (Md5Result)
                        {
                            fileResult = new FileResult(fileInfo)
                            {
                                MatchedRule = ClassifierRule
                            };
                            Mq.FileResult(fileResult);
                        }
                        return;

                    default:
                        Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
                        return;
                    }
                }
                else
                {
                    Mq.Trace("The following file was bigger than the MaxSizeToGrep config parameter:" + fileInfo.FullName);
                }
            }
            catch (UnauthorizedAccessException)
            {
                Mq.Error($"Not authorized to access file: {fileInfo.FullName}");
                return;
            }
            catch (IOException e)
            {
                Mq.Error($"IO Exception on file: {fileInfo.FullName}. {e.Message}");
                return;
            }
            catch (Exception e)
            {
                Mq.Error(e.ToString());
                return;
            }
        }
Beispiel #12
0
        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);
        }
Beispiel #13
0
        public void ClassifyContent(FileInfo fileInfo)
        {
            BlockingMq Mq = BlockingMq.GetMq();
            FileResult fileResult;

            try
            {
                if (MyOptions.MaxSizeToGrep >= fileInfo.Length)
                {
                    // figure out if we need to look at the content as bytes or as string.
                    switch (ClassifierRule.MatchLocation)
                    {
                    case MatchLoc.FileContentAsBytes:
                        byte[] fileBytes = File.ReadAllBytes(fileInfo.FullName);
                        if (ByteMatch(fileBytes))
                        {
                            fileResult = new FileResult(fileInfo)
                            {
                                MatchedRule = ClassifierRule
                            };
                            // if the file was list-only, don't bother sending a result back to the user.
                            if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite)
                            {
                                return;
                            }
                            ;
                            Mq.FileResult(fileResult);
                        }
                        return;

                    case MatchLoc.FileContentAsString:
                        try
                        {
                            string fileString;

#if ULTRASNAFFLER
                            // if it's an office doc or a PDF or something, parse it to a string first i guess?
                            List <string> parsedExtensions = new List <string>()
                            {
                                ".doc", ".docx", ".xls", ".xlsx", ".eml", ".msg", ".pdf", ".ppt", ".pptx", ".rtf", ".docm", ".xlsm", ".pptm", ".dot", ".dotx", ".dotm", ".xlt", ".xlsm", ".xltm"
                            };

                            if (parsedExtensions.Contains(fileInfo.Extension))
                            {
                                fileString = ParseFileToString(fileInfo);
                            }
                            else
                            {
                                fileString = File.ReadAllText(fileInfo.FullName);
                            }
#else
                            fileString = File.ReadAllText(fileInfo.FullName);
#endif
                            TextClassifier textClassifier = new TextClassifier(ClassifierRule);
                            TextResult     textResult     = textClassifier.TextMatch(fileString);
                            if (textResult != null)
                            {
                                fileResult = new FileResult(fileInfo)
                                {
                                    MatchedRule = ClassifierRule,
                                    TextResult  = textResult
                                };
                                // if the file was list-only, don't bother sending a result back to the user.
                                if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite)
                                {
                                    return;
                                }
                                ;
                                Mq.FileResult(fileResult);
                            }
                        }
                        catch (UnauthorizedAccessException)
                        {
                            return;
                        }
                        catch (IOException)
                        {
                            return;
                        }
                        return;

                    case MatchLoc.FileLength:
                        bool lengthResult = SizeMatch(fileInfo);
                        if (lengthResult)
                        {
                            fileResult = new FileResult(fileInfo)
                            {
                                MatchedRule = ClassifierRule
                            };

                            // if the file was list-only, don't bother sending a result back to the user.
                            if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite)
                            {
                                return;
                            }
                            ;
                            Mq.FileResult(fileResult);
                        }
                        return;

                    case MatchLoc.FileMD5:
                        bool Md5Result = MD5Match(fileInfo);
                        if (Md5Result)
                        {
                            fileResult = new FileResult(fileInfo)
                            {
                                MatchedRule = ClassifierRule
                            };

                            // if the file was list-only, don't bother sending a result back to the user.
                            if (!fileResult.RwStatus.CanRead && !fileResult.RwStatus.CanModify && !fileResult.RwStatus.CanWrite)
                            {
                                return;
                            }
                            ;
                            Mq.FileResult(fileResult);
                        }
                        return;

                    default:
                        Mq.Error("You've got a misconfigured file ClassifierRule named " + ClassifierRule.RuleName + ".");
                        return;
                    }
                }
                else
                {
                    Mq.Trace("The following file was bigger than the MaxSizeToGrep config parameter:" + fileInfo.FullName);
                }
            }
            catch (UnauthorizedAccessException)
            {
                Mq.Error($"Not authorized to access file: {fileInfo.FullName}");
                return;
            }
            catch (IOException e)
            {
                Mq.Error($"IO Exception on file: {fileInfo.FullName}. {e.Message}");
                return;
            }
            catch (Exception e)
            {
                Mq.Error(e.ToString());
                return;
            }
        }