// post processing - added Dec 5, 2016
 public static void FindStraySQLServers(NetworkTrace trace)
 {
     // go through each SQL Server
     // if only 1 conversation, see if conversation client IP and port match another SQL Server's Server IP and port
     // if so, reverse the covnersation and add to the other SQl Server and get rid of this one
     foreach (SQLServer s in trace.sqlServers)
     {
         if (s.conversations.Count == 1)
         {
             ConversationData c  = (ConversationData)(s.conversations[0]);
             SQLServer        s2 = trace.FindSQLServer(c.sourceIP, c.sourceIPHi, c.sourceIPLo, c.sourcePort, c.isIPV6);
             if (s2 != null)
             {
                 reverseSourceDest(c);
                 s2.conversations.Add(c);
                 s.conversations.Remove(c);
                 s.sqlIP   = 0;
                 s.sqlIPHi = 0;
                 s.sqlIPLo = 0;
                 s.sqlPort = 0;
             }
         }
     }
     // remove bad entries from the trace.sqlServes collection - iterate backwards because of Remove method
     for (int i = trace.sqlServers.Count - 1; i > 0; i--)
     {
         SQLServer s = (SQLServer)(trace.sqlServers[i]);
         if (s.conversations.Count == 0 && s.sqlPort == 0 && s.sqlIP == 0 && s.sqlIPHi == 0 & s.sqlIPLo == 0)
         {
             trace.sqlServers.RemoveAt(i);
         }
     }
 }
        public long pktmonMaxDelay          = 0;               // - set in OutputText.DisplayDelayedPktmonEvents

        public void AddFrame(FrameData f, NetworkTrace t)      // replaces adding the frame directly to the frames ArrayList
        {
            //
            // Rubrick:
            //
            // If the frame is not a pktmon frame, add the the frames ArrayData and exit. This will be the norm for almost all traces analyzed.
            // If the frame is a pktmon frame, find the previous instance of this frame (go backwards for efficiency).
            //    If no previous instance, create the pktmonComponentFrames ArrayList and add the frame to that and add the frame to the ArrayList and exit.
            //    If there is a previous frame, add the frame to the pktmonComponentFrames ArrayList of that frame and exit.
            //

            if (f.pktmon == null)
            {
                frames.Add(f);
                t.frames.Add(f);
            }
            else
            {
                // search and add - use the limit of 20 frames. For a single conversation, it can't be out of sequence that much
                if (f.pktmon.eventID == 170)  // drop packet event
                {
                    this.hasPktmonDroppedEvent = true;
                    this.pktmonDropReason      = f.pktmon.DropReason;
                }
                const int BACK_COUNT_LIMIT = 20;
                int       backCount        = 0;
                for (int i = this.frames.Count - 1; i >= 0; i--)
                {
                    FrameData priorFrame = (FrameData)frames[i];
                    backCount++;
                    if (priorFrame.pktmon.PktGroupId == f.pktmon.PktGroupId)
                    {
                        priorFrame.pktmonComponentFrames.Add(f);  // found withing BACK_COUNT_LIMIT frames
                        return;
                    }
                    if (backCount >= BACK_COUNT_LIMIT)
                    {
                        break;
                    }
                }
                // not found within BACK_COUNT_LIMIT frames, so assume it's the first
                f.pktmonComponentFrames = new ArrayList();
                f.pktmonComponentFrames.Add(f);  // make sure the frame is first in the list, so we don't have to treat it differently when performing pktmon analyses
                frames.Add(f);
                t.frames.Add(f);
            }
        }
        public static void Process(NetworkTrace trace)
        {
            foreach (ConversationData c in trace.conversations)
            {
                if (c.sourcePort == 88)
                {
                    TDSParser.reverseSourceDest(c);
                }


                if (c.destPort != 88)
                {
                    continue;
                }


                KerberosData KerbData = null;

                try
                {
                    if (c.isUDP) // UDP
                    {
                        KerbData = ProcessUDP(c);
                    }
                    else        // TCP
                    {
                        KerbData = ProcessTCP(c);
                    }
                    // ignore non KRB_TGS requests
                    // ignore responses without an associated request - we don't want to log errors for unidentified request types
                    // ignore SNames we don't know what they are ... or how to read them
                    //if (KerbData.RequestType == MessageTypes.KRB_TGS_REQ
                    //   && (KerbData.SNameType == 2 || KerbData.SNameType == 10))
                    if (KerbData.RequestType == MessageTypes.KRB_TGS_REQ)
                    {
                        trace.KerbResponses.Add(KerbData);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Exception during Kerberos processing." + "\r\n" + ex.Message + "\r\n" + ex.StackTrace);
                    Program.logDiagnostic("Exception during Kerberos processing." + "\r\n" + ex.Message + "\r\n" + ex.StackTrace);
                } // catch
            }     // foreach
        }         // void Process(...)
 // post processing
 public static void FindStraySQLConversations(NetworkTrace trace)
 {
     // go through all non-SQL conversations and see if they are on a port that SQL is using and set the flag, reverse source and dest if necessary, and add to SQLServers.conversations
     foreach (ConversationData c in trace.conversations)
     {
         if (c.isSQL == false)
         {
             SQLServer server = trace.FindSQLServer(c);
             if (server != null)
             {
                 c.isSQL = true;
                 server.conversations.Add(c);
                 if (c.destIP != server.sqlIP || c.destIPHi != server.sqlIPHi || c.destIPLo != server.sqlIPLo || c.destPort != server.sqlPort)
                 {
                     reverseSourceDest(c);
                 }
             }
         }
     }
 }
        static void Main(string[] args)
        {
            string fileSpec    = null;
            string outFile     = null;
            string diagOutFile = null;
            string statOutFile = null;

            ArrayList sqlHints = new ArrayList();

            ActivityTimer T = new ActivityTimer();

            //
            // Set command-line rules and parse the command-line
            //

            CommandLineParser cp = new CommandLineParser();

            cp.AddRule(new ArgRule("", true, false, true, true));                 // file name is required
            cp.AddRule(new ArgRule("output", true, false, true, false));          // -output filename is optional and case-insensitive, one time only
            cp.AddRule(new ArgRule("sql", true, true, false, false));             // -sql 10:10:10:10,1433 -sql 10.10.10.11,1433 ... -sql ip,port     0..n times   (ipv4 or ipv6)
            cp.AddRule(new ArgRule("convList", false, false, true, false));       // -convList     outputs a rather lengthy report segment that is normally not required
            cp.AddRule(new ArgRule("filterFmt", true, false, true, false));       // -filterFmt NETMON|WireShark   replaces the Client IP address and port columns with a filter string, instead
            string ruleViolation = cp.Parse(args);

            if (ruleViolation != "")
            {
                Console.WriteLine("Bad arguments: " + ruleViolation);
                displayUsage();
                return;           // exit the application
            }
            else  // for case-insensitive argument names, use lower-case to match
            {
                ArrayList values = null;
                //
                // set filename -> fileSpec
                //
                values   = cp.GetArgs("");
                fileSpec = ((CommandLineArgs)values[0]).value;  // required, so it's here or we'd get a ruleViolation earlier
                //
                // set outputFile -> outFile
                //
                values = cp.GetArgs("output");
                if (values.Count != 0)  // argument supplied
                {
                    outFile = ((CommandLineArgs)values[0]).value;
                }
                else // argument omitted
                {
                    outFile = utility.getLogFileName(fileSpec);
                }
                diagOutFile = utility.getDiagLogFileName(outFile);
                statOutFile = utility.getStatLogFileName(outFile);
                //
                // set sqlHints
                //
                values = cp.GetArgs("sql");
                if (values.Count != 0) // argument supplied
                {
                    foreach (CommandLineArgs value in values)
                    {
                        sqlHints.Add(value.value);
                    }
                }
                //
                // set outputConversationList
                //
                values = cp.GetArgs("convlist");
                if (values.Count != 0)
                {
                    outputConversationList = true;
                }
                //
                // set filterFormat
                //
                values = cp.GetArgs("filterfmt");
                if (values.Count != 0)
                {
                    string value = ((CommandLineArgs)values[0]).value.ToUpper();
                    switch (value)
                    {
                    case "NETMON":
                    {
                        filterFormat = "N";
                        break;
                    }

                    case "WIRESHARK":
                    {
                        filterFormat = "W";
                        break;
                    }

                    case "AUTO":
                    {
                        filterFormat = "A";          // gets set to N or W based on the file type of the first file opened
                        break;
                    }

                    default:
                    {
                        Console.WriteLine("Bad arguments: filterFmt");
                        displayUsage();
                        return;                   // exit the application
                    }
                    }
                }
            }

            commandLine = string.Join(" ", args);

            try
            {
                diagFile = new StreamWriter(diagOutFile);

                // output diagnostic header
                logDiagnostic("SQL Server Network Analyzer " + VERSION_NUMBER);
                logDiagnostic("Command line arguments:      " + string.Join(" ", args));
                logDiagnostic("Analysis run on:             " + DateTime.Now.ToString(utility.DATE_FORMAT));

                // open log file
                CurrentActivity = "opening log file: " + outFile;
                logFile         = new StreamWriter(outFile);

                NetworkTrace Trace = new NetworkTrace();

                // add SQL hints
                foreach (string value in sqlHints)
                {
                    bool   isIPV6 = false;
                    ushort port = 0;
                    uint   ipv4 = 0;
                    ulong  ipv6hi = 0, ipv6lo = 0;
                    utility.ParseIPPortString(value, ref isIPV6, ref port, ref ipv4, ref ipv6hi, ref ipv6lo);
                    Trace.GetSQLServer(ipv4, ipv6hi, ipv6lo, port, isIPV6); // creates an entry in the SQl Server table if not one already - allows for duplicate -sql command-line arguments
                }

                // read files and parse into memory structures
                CurrentActivity = "parsing input file(s) from the folder.";
                Parser.ParseFileSpec(fileSpec, Trace);

                //Post Processing
                CurrentActivity = "processing data.";

                T.start("\nReversing backward conversations");
                Parser.ReverseBackwardConversations(Trace);
                T.stop();

                T.start("Finding retransmitted packets");
                Parser.FindRetransmits(Trace);
                T.stop();

                T.start("Finding retransmitted Keep-Alive packets");
                Parser.FindKeepAliveRetransmits(Trace);
                T.stop();

                T.start("Finding continuation packets");
                Parser.FindContinuationFrames(Trace);
                T.stop();

                T.start("Parsing TDS frames");
                TDSParser.ProcessTDS(Trace);
                T.stop();

                T.start("Finding stray SQL conversations");
                TDSParser.FindStraySQLConversations(Trace);
                T.stop();

                T.start("Finding stray SQL Servers");
                TDSParser.FindStraySQLServers(Trace);
                T.stop();

                T.start("Creating packets from frames for SQL Conversations");
                TDSParser.CreatingPacketsFromFrames(Trace);
                T.stop();

                T.start("Parsing UDP frames");
                SSRPParser.ProcessUDP(Trace);
                T.stop();

                T.start("Parsing DNS frames");
                NameResolutionParser.ProcessUDP(Trace);
                T.stop();

                T.start("Parsing Kerberos frames");
                //CurrentActivity = "analyzing data.";
                KerberosParser.Process(Trace);
                T.stop();

                T.start("Locating Domain Controllers");
                //CurrentActivity = "analyzing data.";
                DomainControllerParser.Process(Trace);
                T.stop();

                //Analysis

                CurrentActivity = "writing report.";
                statFile        = new StreamWriter(statOutFile);
                OutputText.TextReport(Trace);
                statFile.Close();
                statFile = null;
            }
            catch (Exception ex)
            {
                Console.WriteLine("An error occurred while " + CurrentActivity + "\r\n" + ex.Message + "\r\n" + ex.StackTrace);
                logDiagnostic("An error occurred while " + CurrentActivity + "\r\n" + ex.Message + "\r\n" + ex.StackTrace);
            }
            finally
            {
                if (logFile != null)
                {
                    logFile.Close();
                }
                if (diagFile != null)
                {
                    diagFile.Close();
                }
                if (statFile != null)
                {
                    statFile.Close();
                }
            }
        }
Example #6
0
        public static void ProcessUDP(NetworkTrace trace)
        {
            foreach (ConversationData c in trace.conversations)
            {
                if (c.isUDP && c.sourcePort == 1434)
                {
                    TDSParser.reverseSourceDest(c);
                }
                //parse only UDP conversations that are on port 1434
                if ((!c.isUDP) || ((c.isUDP) && (c.destPort != 1434)))
                {
                    continue;
                }


                SSRPData SSRPRequest = trace.GetSSRPRequest(c.destIP, c.destIPHi, c.destIPLo, c.isIPV6);

                if (!SSRPRequest.hasConversation(c))
                {
                    SSRPRequest.conversations.Add(c);
                }

                foreach (FrameData fd in c.frames)
                {
                    try
                    {
                        if ((byte)(fd.payload[0]) == (byte)3) // CLNT_UCAST_EX
                        {
                            SSRPRequest.hasResponse = false;
                        }

                        else if ((byte)(fd.payload[0]) == (byte)4) // Request for specific instance  (CLNT_UCAST_INST)
                        {
                            SSRPRequest.hasResponse = false;

                            if (c.frames.Count == 1)
                            {
                                SSRPRequest.hasNoResponse = true;
                            }

                            ushort Length = utility.ReadUInt16(fd.payload, 1);
                            SSRPRequest.instanceRequested = utility.ReadAnsiString(fd.payload, 3, Length);
                            //SSRPRequest.clientPort = c.sourcePort;
                            //SSRPRequest.clientIP = (c.isIPV6) ? utility.FormatIPV6Address(c.sourceIPHi, c.sourceIPLo) : utility.FormatIPV4Address(c.sourceIP);
                            SSRPRequest.sqlIP   = c.destIP;
                            SSRPRequest.sqlIPHi = c.destIPHi;
                            SSRPRequest.sqlIPLo = c.destIPLo;
                        }
                        else if ((byte)(fd.payload[0]) == (byte)5) // Response of specifric instance (SVR_RESP)
                        {
                            SSRPRequest.hasResponse = true;
                            ushort Length   = utility.ReadUInt16(fd.payload, 1);
                            String Response = utility.ReadAnsiString(fd.payload, 3, Length);
                            ParseSSRPResponse(Response, SSRPRequest, trace);
                            //if (SSRPRequest.sqlPort != 0)
                            //{
                            //    SQLServer s = trace.GetSQLServer(SSRPRequest.sqlIP, SSRPRequest.sqlIPHi, SSRPRequest.sqlIPLo, SSRPRequest.sqlPort, SSRPRequest.isIPV6);
                            //    if (s != null)
                            //    {
                            //        if (s.sqlHostName == "") s.sqlHostName = SSRPRequest.sqlHostName;
                            //        if (s.instanceName == "") s.instanceName = SSRPRequest.instanceName;
                            //        if (s.isClustered == "") s.isClustered = SSRPRequest.isClustered;
                            //        if (s.serverVersion == "") s.serverVersion = SSRPRequest.serverVersion;
                            //        if (s.namedPipe == "") s.namedPipe = SSRPRequest.namedPipe;
                            //    }
                        }
                    }
                    catch (Exception ex)
                    {
                        Program.logDiagnostic("SSRP Parser: Problem parsing frame " + fd.frameNo + " in file " + fd.file.filePath + ".");
                        Program.logDiagnostic(ex.Message);
                    }
                }
            }
        } // Process UDP
Example #7
0
        public static void ParseSSRPResponse(String ssrpResponse, SSRPData SSRPRequest, NetworkTrace trace)
        {
            if (ssrpResponse.Length <= 0)
            {
                return;
            }

            //the client either (i) sends a single request to a specific machine and expects a single response, or
            //(ii) broadcasts or multicasts a request to the network and expects zero or more responses from different
            //discovery services on the network - Page# 30 in SSRP Specs.

            //Response can contain more than one server informaton.
            //Each server info separated by ;;
            String[] Servers = ssrpResponse.Split(new string[] { ";;" }, StringSplitOptions.None);

            foreach (var Server in Servers)
            {
                String[] Tokens = Server.Split(';');

                SSRPRequest.sqlHostName   = GetUDPToken(Tokens, "ServerName");
                SSRPRequest.instanceName  = GetUDPToken(Tokens, "InstanceName");
                SSRPRequest.isClustered   = GetUDPToken(Tokens, "IsClustered");
                SSRPRequest.serverVersion = GetUDPToken(Tokens, "Version");
                SSRPRequest.namedPipe     = GetUDPToken(Tokens, "np");
                string portString = GetUDPToken(Tokens, "tcp");

                SSRPRequest.sqlPort = portString.Length > 0 ? Convert.ToUInt16(portString) : (ushort)0;

                if (SSRPRequest.sqlPort != 0)
                {
                    SQLServer s = trace.GetSQLServer(SSRPRequest.sqlIP,
                                                     SSRPRequest.sqlIPHi,
                                                     SSRPRequest.sqlIPLo,
                                                     SSRPRequest.sqlPort,
                                                     SSRPRequest.isIPV6);
                    if (s != null)
                    {
                        if (s.sqlHostName == "")
                        {
                            s.sqlHostName = SSRPRequest.sqlHostName;
                        }
                        if (s.instanceName == "")
                        {
                            s.instanceName = SSRPRequest.instanceName;
                        }
                        if (s.isClustered == "")
                        {
                            s.isClustered = SSRPRequest.isClustered;
                        }
                        if (s.serverVersion == "")
                        {
                            s.serverVersion = SSRPRequest.serverVersion;
                        }
                        if (s.namedPipe == "")
                        {
                            s.namedPipe = SSRPRequest.namedPipe;
                        }
                    }
                }
            }
        }
        public static void ProcessUDP(NetworkTrace trace)
        {
            string[] DnsReturnMessage = new string[] {
                "Success",
                "Format error; DNS server did not understand the update request.",
                "DNS server encountered an internal error, such as a forwarding timeout.",
                "A name that should exist does not exist.",
                "DNS server does not support the specified Operation code.",
                "DNS server refuses to perform the update.",
                "A name that should not exist does exist.",
                "A resource record set that should not exist does exist.",
                "A resource record set that should exist does not exist.",
                "DNS server is not authoritative for the zone named in the Zone section.",
                "A name used in the Prerequisite or Update sections is not within the zone specified by the Zone section.",
                "Invalid return code.",
                "Invalid return code.",
                "Invalid return code.",
                "Invalid return code.",
                "Reserved."
            };

            foreach (ConversationData c in trace.conversations)
            {
                //DNS traffic is over UDP. If the current converstion is not UDP then skip that non DNS conversation.
                if (!c.isUDP)
                {
                    continue;
                }

                //Skip the conversation, if  its just UDP, but not DNS
                if ((c.isUDP) && c.destPort != 53)
                {
                    continue;
                }

                trace.DNSRequestCount++;

                try
                {
                    //Parse the DNS frames of the conversation.
                    foreach (FrameData fd in c.frames)
                    {
                        DNS DNSresponse = new DNS();

                        if (fd.payloadLength < 1)
                        {
                            continue;
                        }



                        //DNS data starts at 42nd byte of the total payload. so the payload = (Total Payload - 42) bytes.
                        TDSReader ByteReader = new TDSReader(fd.payload, 0, -1, fd.payloadLength);


                        ByteReader.ReadBigEndianUInt16();     //Skip over the query ID.

                        //Read the Flags and convert into bytes.
                        int FlagsHigh = ByteReader.ReadByte();
                        int FlagsLow  = ByteReader.ReadByte();

                        if ((FlagsHigh & (int)0x80) == (int)0x0)     // DNS request
                        {
                            //DNSresponse.srcServerIP  = c.sourceIP.ToString();
                            DNSresponse.dnsServerIP = c.destIP.ToString();
                        }
                        else if ((FlagsHigh & (int)0x80) == (int)0x80) // DNS response
                        {
                            int rCode = FlagsLow & 0x0F;               // should be between 0 - 15.

                            //DNSresponse.srcServerIP= c.destIP.ToString();
                            DNSresponse.dnsServerIP = c.sourceIP.ToString();
                            DNSresponse.frameNo     = fd.frameNo;
                            DNSresponse.TimeStamp   = new DateTime(((FrameData)c.frames[c.frames.Count - 1]).ticks).ToString(utility.TIME_FORMAT);
                            //new DateTime(((FrameData)trace.frames[0]).ticks).ToString(utility.TIME_FORMAT);
                            DNSresponse.errorCode = rCode;
                            DNSresponse.ErrorDesc = DnsReturnMessage[rCode];

                            //Question Count  - 2 bytes
                            DNSresponse.QuestionCount = ByteReader.ReadInt16();

                            //Answer Count - 2 bytes
                            DNSresponse.AnswerCount = ByteReader.ReadInt16();

                            //Skip 2 bytes - Name Server count
                            ByteReader.ReadInt16();

                            //Skip 2 bytes - Additional count
                            ByteReader.ReadInt16();

                            //Start reading the QName
                            //13th byte of the DNS Payload - payload[12]
                            byte   length = ByteReader.ReadByte();
                            string Name   = "";
                            while (length != 0)
                            {
                                Name  += (Name == "" ? "" : ".") + ByteReader.ReadAnsiString(length);
                                length = ByteReader.ReadByte();
                            }

                            DNSresponse.nameReqested = Name;
                            DNSresponse.convData     = c;
                            //DNSresponse.srcPort = c.sourcePort;

                            // Console.WriteLine(fd.file.filePath + "\t" + fd.frameNo.ToString());
                            if (rCode != (int)DNSReturnCodes.NOERROR)
                            {
                                trace.DNSResponses.Add(DNSresponse);
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error parsing DNS conversation: " + ex.Message + "\r\n" + ex.StackTrace);
                    Program.logDiagnostic("Error parsing DNS conversation: " + ex.Message + "\r\n" + ex.StackTrace);
                }
            }
        }
        // post processing
        public static void ProcessTDS(NetworkTrace trace)
        {
            foreach (ConversationData c in trace.conversations)
            {
                long payLoadLength      = 0;
                int  tdsClientSource    = 0;
                int  tdsClientDest      = 0;
                int  tdsServerSource    = 0;
                int  tdsServerDest      = 0;
                int  tdsOtherFrames     = 0;
                int  switchClientServer = 0;  // 0 = do not switch; 1+ = switch - only increment if c.hasApplicationData and PostLoginResponse are false

                if (c.isUDP)
                {
                    continue;
                }

                //if (c.sourcePort == 53388 || c.destPort == 53388)  // place to put breakpoint when trying to isolate a specifc conversation
                //    Console.WriteLine();

                // bypass client or server port < 500 - the well-known ports
                // SQL will not configure itself in this range with Dynamic Ports - uses a port from the ephemeral range
                //    Well Known Ports:      0 through   1023
                //    Registered Ports:   1024 through  49151
                //    Dynamic/Private :  49152 through  65535
                // had some customer use ports 1021-1024 for SQL ports - lower limit to avoid common services: HTTP, HTTPS, Kerberos, SMB, etc., all below 500

                if ((c.sourcePort < 500) || (c.destPort < 500))
                {
                    continue;                                              // to avoid confusing encrypted traffic on common services with SQL traffic. Override with /SQL command-line switch
                }
                //TLS//SSL Payload ?
                foreach (FrameData fd in c.frames)
                {
                    try
                    {
                        // increased < 7 to < 9. to fix exception in TCP ETW                     *** TODO TODO TODO
                        // weed out non-TDS packets
                        //if (fd.payloadLength < 7) continue;  // ATTENTION payload is 8 bytes   *** should we be < 8 instead ??? *** TODO TODO TODO
                        if (fd.payloadLength < 9)
                        {
                            continue;                         // ATTENTION payload is 8 bytes
                        }
                        // ignore continuation packets until we get a complete TDS message parser built
                        if (fd.isContinuation)
                        {
                            continue;
                        }

                        // bypass HTTP
                        if ((utility.ReadAnsiString(fd.payload, 0, 3).ToUpper() == "GET") ||
                            (utility.ReadAnsiString(fd.payload, 0, 4).ToUpper() == "POST") ||
                            (utility.ReadAnsiString(fd.payload, 0, 4).ToUpper() == "HTTP")) // other keywords much less common
                        {
                            break;                                                          // exit the entire conversation
                        }

                        int firstByte = fd.payload[0];

                        //if (c.sourcePort == 1431)  // debugging construct
                        //{

                        //    if (fd.frameNo == 428)
                        //        Console.WriteLine(fd.frameNo + "   " + firstByte + "   " + fd.FormatPayloadChars(30));
                        //}

                        // make sure we have a supported packet type
                        if ((firstByte != (int)TDSPacketType.SQLBATCH) &&   //    1
                            (firstByte != (int)TDSPacketType.LOGIN) &&      //    2
                            (firstByte != (int)TDSPacketType.RPC) &&        //    3
                            (firstByte != (int)TDSPacketType.RESPONSE) &&   //    4
                            (firstByte != (int)TDSPacketType.ATTENTION) &&  //    6
                            (firstByte != (int)TDSPacketType.BULKLOAD) &&   //    7
                            (firstByte != (int)TDSPacketType.DTC) &&        //   14
                            (firstByte != (int)TDSPacketType.SSPI) &&       //   17
                            (firstByte != (int)TDSPacketType.PRELOGIN) &&   //   18
                            (firstByte != (int)TDSPacketType.APPDATA))      //   23
                        {
                            continue;
                        }

                        // read header parts that we are interested in
                        bool   tdsEOM    = (fd.payload[1] & 0x1) == 1;
                        ushort tdsLength = utility.B2UInt16(fd.payload, 2);

                        // APPDATA does not have a TDS payload, but a TLS payload, so skip these tests if APPDATA
                        if (firstByte != (int)TDSPacketType.APPDATA)
                        {
                            // TDS header Length argument needs to be non-zero and also >= payload length
                            if (tdsLength == 0 || tdsLength < fd.payloadLength)
                            {
                                continue;
                            }

                            if (fd.payload[6] > 1)
                            {
                                continue;                    // TDS Continuous Response packets can have greater values, but we are ignoring them right now
                            }
                            // TDS window needs to be 0   -- from TDSView -- TODO understand the reason for this - does MARS have non-Zero value?
                            if (fd.payload[7] != 0)
                            {
                                continue;
                            }
                        }

                        switch (firstByte)
                        {
                        case (byte)TDSPacketType.PRELOGIN:        // can be client or server
                        {
                            byte preloginType    = fd.payload[8]; // first byte after TDS header = prelogin type
                            byte handshakeType   = 0;
                            byte sslMajorVersion = 0;
                            byte sslMinorVersion = 0;

                            if (preloginType == 0)          // Prelogin packet
                            {
                                GetClientPreloginInfo(fd.payload, fd.conversation);
                                c.hasPrelogin = true;
                                if (fd.isFromClient)
                                {
                                    tdsClientSource++;           // looks like SQL is on the destIP side - good
                                }
                                else
                                {
                                    if (c.hasApplicationData == false && c.hasPostLoginResponse == false)
                                    {
                                        switchClientServer++;
                                    }
                                    tdsClientDest++;             // looks like SQL is on the sourceIP side - need to switch later
                                }
                            }
                            else if (preloginType == 0x16)                       // SSL handshake
                            {
                                sslMajorVersion = fd.payload[9];                 // first byte after preLogintype
                                sslMinorVersion = fd.payload[10];                // next byte
                                handshakeType   = fd.payload[13];                // first byte after SSL header = SSL handshake type
                                if (handshakeType == 1 || handshakeType == 0x10) // Client Hello or Client Key Exchange
                                {
                                    if (handshakeType == 1)
                                    {
                                        c.hasClientSSL     = true;
                                        c.tlsVersionClient = translateSSLVersion(sslMajorVersion, sslMinorVersion);
                                        if (sslMajorVersion != 3 || sslMinorVersion != 3)
                                        {
                                            c.hasLowTLSVersion = true;                                                        // mark anything other than TLS 1.2
                                        }
                                    }
                                    if (handshakeType == 0x10)
                                    {
                                        c.hasKeyExchange = true;
                                    }
                                    if (fd.isFromClient)
                                    {
                                        tdsClientSource++;           // looks like SQL is on the destIP side - good
                                    }
                                    else
                                    {
                                        if (c.hasApplicationData == false && c.hasPostLoginResponse == false)
                                        {
                                            switchClientServer++;
                                        }
                                        tdsClientDest++;             // looks like SQL is on the sourceIP side - need to switch later
                                    }
                                }
                                else if (handshakeType == 2)         // Server Hello -- do we sometimes hit here, or is it just in the TDS RESPONSE version of this logic
                                {
                                    Program.logDiagnostic($"Non-Response Server Hello packet seen in file {fd.file} and frame {fd.frameNo}.");
                                    c.hasServerSSL     = true;
                                    c.tlsVersionServer = translateSSLVersion(sslMajorVersion, sslMinorVersion);
                                    if (sslMajorVersion != 3 || sslMinorVersion != 3)
                                    {
                                        c.hasLowTLSVersion = true;                                                        // mark anything other than TLS 1.2
                                    }
                                    if (fd.isFromClient)
                                    {
                                        tdsServerSource++;           // looks like SQL is on the sourceIP side - need to switch later
                                    }
                                    else
                                    {
                                        tdsServerDest++;             // looks like SQL is on the destIP side - good
                                    }
                                }
                            }
                            else if (preloginType == 0x14)         // Cipher exchange - could be client or server
                            {
                                c.hasCipherExchange = true;
                                tdsOtherFrames++;          // since could be client or server
                            }

                            //accumulate the payload.
                            payLoadLength += fd.payload.Length;

                            break;
                        }

                        case (byte)TDSPacketType.APPDATA:        // 0x17 = Application data
                        {
                            c.hasApplicationData = true;
                            tdsOtherFrames++;          // since could be client or server

                            //accumulate the payload.
                            payLoadLength += fd.payload.Length;

                            break;
                        }

                        case (byte)TDSPacketType.LOGIN:
                        {
                            //accumulate the payload. *** normally, we should not see this packet unencrypted ***
                            c.hasLogin7    = true;
                            payLoadLength += fd.payload.Length;

                            if (fd.isFromClient)
                            {
                                tdsClientSource++;           // looks like SQL is on the destIP side - good
                            }
                            else
                            {
                                tdsClientDest++;             // looks like SQL is on the sourceIP side - need to switch later
                            }
                            break;
                        }

                        case (byte)TDSPacketType.SSPI:
                        {
                            c.hasIntegratedSecurity = true;

                            //accumulate the payload.
                            payLoadLength += fd.payload.Length;

                            // Check for NTLM Response Message - TODO if this isn't true, do we abort the TDS parsing for this conversation?
                            if (fd.payloadLength > 16)
                            {
                                if ((utility.ReadAnsiString(fd.payload, 8, 7) == "NTLMSSP") &&           // NTLM signature
                                    (fd.payload[15] == 0) &&                                             // null terminated
                                    (fd.payload[16] == 3))                                               // Type = Authenticate Message
                                {
                                    c.hasNTLMResponse = true;

                                    //Parse User name and domain name
                                    //check are they both 0 length? if yes, set a flag in conversation data that indicates null credentials
                                    c.hasNullNTLMCreds = AreTheCredentialsNull(fd);

                                    if (fd.isFromClient == false && c.hasApplicationData == false && c.hasPostLoginResponse == false)
                                    {
                                        switchClientServer++;
                                    }
                                }
                            }

                            if (fd.isFromClient)
                            {
                                tdsClientSource++;           // looks like SQL is on the destIP side - good
                            }
                            else
                            {
                                tdsClientDest++;             // looks like SQL is on the sourceIP side - need to switch later
                            }
                            break;
                        }

                        case (byte)TDSPacketType.RPC:
                        case (byte)TDSPacketType.SQLBATCH:
                        case (byte)TDSPacketType.DTC:
                        {
                            //accumulate the payload.
                            payLoadLength         += fd.payload.Length;
                            c.hasPostLoginResponse = true;         // if we're doing this, login has already succeeded

                            /*******
                             * if (firstByte == (int)TDSPacketType.SQLBATCH)
                             * {
                             *
                             *  int Length = (int) utility.ReadUInt32(fd.payload, 2);
                             *
                             * }
                             **************/

                            if (fd.isFromClient)
                            {
                                tdsClientSource++;           // looks like SQL is on the destIP side - good
                            }
                            else
                            {
                                tdsClientDest++;             // looks like SQL is on the sourceIP side - need to switch later
                            }
                            break;
                        }

                        case (byte)TDSPacketType.ATTENTION:      // added Dec 5, 2016
                        {
                            //accumulate the payload.
                            payLoadLength         += fd.payload.Length;
                            c.hasPostLoginResponse = true;         // if we're doing this, login has already succeeded
                            c.AttentionTime        = fd.ticks;

                            if (fd.isFromClient)
                            {
                                tdsClientSource++;           // looks like SQL is on the destIP side - good
                            }
                            else
                            {
                                tdsClientDest++;             // looks like SQL is on the sourceIP side - need to switch later
                            }
                            break;
                        }

                        case (byte)TDSPacketType.RESPONSE:      //0x4
                        {
                            // process error responses
                            if (fd.payload[8] == (byte)TDSTokenType.ERROR)
                            {
                                c.Error      = utility.ReadUInt32(fd.payload, 11);
                                c.ErrorState = fd.payload[15];
                                int ErrorLen = (int)fd.payload[17];
                                c.ErrorMsg = utility.ReadUnicodeString(fd.payload, 19, ErrorLen);
                            }
                            //pre-login info from Server.
                            // if (tokenOffset(fd.payload, (byte)TDSTokenType.PRELOGINRESPONSE) > 7)  // response header is offset 0..7 - need to fix this routine
                            else if (fd.payload[8] == (byte)TDSTokenType.PRELOGINRESPONSE)         // only 1 token in the payload
                            {
                                GetServerPreloginInfo(fd.payload, fd.conversation);
                                c.hasPreloginResponse = true;
                                if (fd.isFromClient && c.hasApplicationData == false && c.hasPostLoginResponse == false)
                                {
                                    switchClientServer++;
                                }
                            }
                            else if (fd.payload[8] == 0x16 && fd.payloadLength > 10) // SSL
                            {
                                byte preloginType    = fd.payload[8];                // first byte after TDS header = prelogin type
                                byte handshakeType   = fd.payload[13];               // first byte after SSL header = SSL handshake type
                                byte sslMajorVersion = fd.payload[9];                // first byte after preLogintype
                                byte sslMinorVersion = fd.payload[10];               // next byte
                                if (handshakeType == 2)                              // Server Hello
                                {
                                    c.hasServerSSL     = true;
                                    c.tlsVersionServer = translateSSLVersion(sslMajorVersion, sslMinorVersion);
                                    if (sslMajorVersion != 3 || sslMinorVersion != 3)
                                    {
                                        c.hasLowTLSVersion = true;                                                        // mark anything other than TLS 1.2
                                    }
                                    if (fd.isFromClient)
                                    {
                                        tdsServerSource++;           // looks like SQL is on the sourceIP side - need to switch later
                                    }
                                    else
                                    {
                                        tdsServerDest++;             // looks like SQL is on the destIP side - good
                                    }
                                }
                            }
                            else if ((fd.payloadLength > 19) &&
                                     (fd.payload[8] == (byte)TDSTokenType.SSPI) &&                               // NTLM Challenge message
                                     (utility.ReadAnsiString(fd.payload, 11, 7) == "NTLMSSP") &&                 // NTLM signature
                                     (fd.payload[18] == 0) &&                                                    // null terminated
                                     (fd.payload[19] == 2))                                                      // type = Challenge Message
                            {
                                c.hasNTLMChallenge = true;
                                if (fd.isFromClient == false && c.hasApplicationData == false && c.hasPostLoginResponse == false)
                                {
                                    switchClientServer++;
                                }
                            }
                            else if ((tokenOffset(fd.payload, (byte)TDSTokenType.ENVCHANGE) > 7) &&          // response header is offset 0..7
                                     (tokenOffset(fd.payload, (byte)TDSTokenType.INFO) > 7) &&
                                     (tokenOffset(fd.payload, (byte)TDSTokenType.LOGINACK) > 7))
                            {
                                c.hasPostLoginResponse = true;
                                c.LoginAckTime         = fd.ticks;

                                try
                                {
                                    // parse LoginAck packet
                                    int offset = tokenOffset(fd.payload, (byte)TDSTokenType.LOGINACK);
                                    c.tdsVersionServer = utility.B2UInt32(fd.payload, offset + 4);
                                    int nameLength = fd.payload[offset + 8] * 2;         // unicode characters
                                    c.serverVersion = fd.payload[offset + nameLength + 9] + "." + fd.payload[offset + nameLength + 10] + "." +
                                                      fd.payload[offset + nameLength + 11] + "." + fd.payload[offset + nameLength + 12];
                                    // parse Info packet - any one, doesn't matter, all have the database name in them
                                    offset       = tokenOffset(fd.payload, (byte)TDSTokenType.INFO);
                                    offset      += utility.ReadUInt16(fd.payload, offset + 9) * 2;                 // unicode characters - and 2-byte length - skip this
                                    nameLength   = fd.payload[offset + 11];                                        // unicode characters
                                    c.serverName = utility.ReadUnicodeString(fd.payload, offset + 12, nameLength); // arg is chars not bytes
                                    // parse ENVCHANGE packet
                                    offset = tokenOffset(fd.payload, (byte)TDSTokenType.ENVCHANGE);
                                    int EnvChangeType = 0;
                                    int tokenLength   = 0;
                                    while (offset != -1)
                                    {
                                        tokenLength   = utility.ReadUInt16(fd.payload, offset + 1);
                                        EnvChangeType = fd.payload[offset + 3];
                                        if (EnvChangeType == 1)          // database name
                                        {
                                            nameLength     = fd.payload[offset + 4];
                                            c.databaseName = utility.ReadUnicodeString(fd.payload, offset + 5, nameLength);
                                        }
                                        else if (EnvChangeType == 0x14)          // server redirection
                                        {
                                            c.RedirectPort = utility.ReadUInt16(fd.payload, offset + 7);
                                            int ServerLen = fd.payload[offset + 9];
                                            c.RedirectServer = utility.ReadUnicodeString(fd.payload, offset + 11, ServerLen);
                                            c.hasReadOnlyIntentConnection = true;
                                        }
                                        offset = tokenOffset(fd.payload, (byte)TDSTokenType.ENVCHANGE, offset + tokenLength + 3);
                                    }
                                }
                                catch (IndexOutOfRangeException ex)
                                {
                                    Program.logDiagnostic("Index out of range exception in file " + trace.files.IndexOf(fd.file) + ", frame " + fd.frameNo + " parsing LoginAck token.");
                                    if (tdsEOM && tdsLength > fd.payloadLength)         // truncated packet
                                    {
                                        Program.logDiagnostic("Payload length read = " + fd.payloadLength + ". TDS length = " + tdsLength);
                                    }
                                }
                                finally
                                {
                                    if (fd.isFromClient == false && c.hasApplicationData == false && c.hasPostLoginResponse == false)
                                    {
                                        switchClientServer++;
                                    }
                                }
                            }

                            //accumulate the payload.
                            payLoadLength += fd.payload.Length;

                            if (fd.isFromClient)
                            {
                                tdsServerSource++;           // looks like SQL is on the sourceIP side - need to switch later
                            }
                            else
                            {
                                tdsServerDest++;             // looks like SQL is on the destIP side - good
                            }
                            break;
                        }

                        case (byte)TDSPacketType.INFO:      //0xAB
                        {
                            break;
                        }
                        } // end switch
                    }     // end try
                    catch (IndexOutOfRangeException ex)
                    {
                        Program.logDiagnostic("Index out of range exception in file " + trace.files.IndexOf(fd.file) + ", frame " + fd.frameNo + ".");
                    }
                    catch (Exception ex)
                    {
                        Program.logDiagnostic("Exception in file " + trace.files.IndexOf(fd.file) + ", frame " + fd.frameNo + ". \n\r" + ex.Message);
                    }
                } // end for each frame in conversation.frames

                //Enable hasTDS flag and isSQL flag - they are FALSE by default
                // Ignore tdsOtherFrames because pure SSL conversations will have 0x14 and 0x16 packet types and show as false positive
                // We may lose a couple of SQL Servers with one fragemented conversation, but they are not likely of interest

                int KeyFrameCount = (c.hasPrelogin ? 1 : 0)
                                    + (c.hasPreloginResponse ? 1 : 0)
                                    + (c.hasClientSSL ? 1 : 0)
                                    + (c.hasServerSSL ? 1 : 0)
                                    + (c.hasKeyExchange ? 1 : 0)
                                    + (c.hasCipherExchange ? 1 : 0)
                                    + (c.hasApplicationData ? 1 : 0)
                                    + (c.hasPostLoginResponse ? 1 : 0);

                //
                // If we see the beginning of the conversation, but there are not enough key frames, then very strong % not SQL.
                // If the number of SQL packets is < 2% of the overall frame count, then not likely SQL. Can adjust this; 1% might be okay, too.
                // There is a possibility we're wrong, but other conversations should correct that.
                //

                if ((c.synCount > 0 && KeyFrameCount < 3) || ((tdsClientSource + tdsServerDest + tdsServerSource + tdsClientDest) * 50) < c.frames.Count)
                {
                    tdsClientSource = tdsServerDest = tdsServerSource = tdsClientDest = 0; // short-cut this option for determining if we have a SQL conversation
                }

                if (KeyFrameCount > 4)  // we're pretty sure this is a SQL Server
                {
                    c.hasTDS    = true;
                    c.isSQL     = true;
                    c.tdsFrames = tdsClientSource + tdsServerSource + tdsClientDest + tdsServerDest + tdsOtherFrames;
                }
                else if (((tdsClientSource + tdsServerDest) > 0) && ((tdsServerSource + tdsClientDest) == 0) ||
                         ((tdsServerSource + tdsClientDest) > 0) && ((tdsClientSource + tdsServerDest) == 0))   // we're sort of sure
                {
                    c.hasTDS    = true;
                    c.isSQL     = true;
                    c.tdsFrames = tdsClientSource + tdsServerSource + tdsClientDest + tdsServerDest + tdsOtherFrames;
                }
                else
                {
                    c.hasTDS = false;
                    c.isSQL  = false;
                }

                // based on the accumulated TDS flags, determine whether we need to switch client and server IP addresses and ports to make SQl on the destination side
                if (((tdsServerDest + tdsClientSource) > 0) && ((tdsClientDest + tdsServerSource) == 0))
                {
                    // SQL IP and port is the conversation destination IP and destination Port
                    // do nothing
                }
                else if (((tdsClientDest + tdsServerSource) > 0) && ((tdsServerDest + tdsClientSource) == 0))
                {
                    // SQL IP and port number are on the source side of the conversation
                    // Reverse everything so it goes on the destination side
                    reverseSourceDest(c);
                }
                else if (switchClientServer > 0)  // based on key frames we parse more completely
                {
                    reverseSourceDest(c);
                }
                else if (((tdsClientDest + tdsServerSource) > 0) && ((tdsServerDest + tdsClientSource) > 0))
                {
                    if (trace.FindSQLServer(c) != null)
                    {
                        c.hasTDS    = true;
                        c.isSQL     = true;
                        c.tdsFrames = tdsClientSource + tdsServerSource + tdsClientDest + tdsServerDest + tdsOtherFrames;
                    }
                    // this is a parsing issue - we should log regardless
                    Program.logDiagnostic("*** TDS conversation with SQL on the both sides. Look into this ***");
                    Program.logDiagnostic(c.ColumnData());
                    Program.logDiagnostic();
                    Program.logDiagnostic("CD: " + tdsClientDest.ToString() + "; SS:" + tdsServerSource.ToString() + "; SD:" + tdsServerDest.ToString() + "; CS:" + tdsClientSource.ToString());
                    Program.logDiagnostic();
                }

                // add the identified SQL Server to the collection and add the conversation to the server conversations
                if (c.isSQL)
                {
                    SQLServer Server = trace.GetSQLServer(c.destIP, c.destIPHi, c.destIPLo, c.destPort, c.isIPV6);
                    Server.conversations.Add(c);
                    if (Server.serverVersion == "" && c.serverVersion != null)
                    {
                        Server.serverVersion = c.serverVersion;
                    }
                    if (Server.sqlHostName == "" && c.serverName != null)
                    {
                        Server.sqlHostName = c.serverName;
                    }
                }
            } // for each conversation
        }
Example #10
0
        public static void Process(NetworkTrace trace)
        {
            trace.DomainControllers = new System.Collections.ArrayList();
            DomainController d = null;

            // Locate TCP and UDP conversations with server ports 53 (DNS) and 88 (KERBEROS) and 389 (LDAP)

            foreach (ConversationData c in trace.conversations)
            {
                if (c.destPort == 53 /* DNS */ || c.destPort == 88 /* Kerberos */ || c.destPort == 389 /* LDAP */)
                {
                    d = trace.GetDomainController(c.destIP, c.destIPHi, c.destIPLo, c.isIPV6);
                    if (c.destPort == 53)
                    {
                        d.DNSPort53Count++;
                    }
                    if (c.destPort == 88)
                    {
                        d.KerbPort88Count++;
                    }
                    if (c.destPort == 389)
                    {
                        d.LDAPPort389Count++;
                    }
                }
            }

            // Find any stray conversations with the DC

            foreach (ConversationData c in trace.conversations)
            {
                d = trace.FindDomainController(c);
                if (d != null)
                {
                    d.conversations.Add(c);
                }
            }

            // Find MSRPC Conversations and Port

            foreach (DomainController dc in trace.DomainControllers)
            {
                foreach (ConversationData c in dc.conversations)
                {
                    if (c.isUDP == false && c.destPort > 1000)  // ignore low port #s
                    {
                        // potential MSRPC traffic
                        foreach (FrameData f in c.frames)
                        {
                            ushort Port = c.destPort;
                            if (isMSRPC(f.payload))
                            {
                                dc.MSRPCPortCount++;
                                if (dc.MSRPCPort == 0)
                                {
                                    dc.MSRPCPort = Port;
                                }
                                else if (dc.MSRPCPort != Port)
                                {
                                    dc.hasMultipleMSRPCPorts = true;
                                }
                                break;
                            }
                        }
                    }
                }
            }
        }