///////////////////////////////////////////////////// // // // LoadAgentSettings() // // // ///////////////////////////////////////////////////// //Description: Loads settings from the agent config file. // //Returns: true if successful ////////////////////////////////////////////////////// private static unsafe bool LoadAgentSettings(ref Dictionary <string, string> AgentSettings) { //use XML settings file in current directory - "CwAgentConfiguration.xml" //this will allow us to deserialize the XML data into class structures CwXML xml = new CwXML(); CwXML.CodewordSettingsTemplate cst = new CwXML.CodewordSettingsTemplate(); try { cst = xml.LoadSettingsXML("CwAgentConfiguration.xml"); } catch (Exception) { return(false); } //copy the settings from the CST object to a more usable dictionary<> struct int count = 0; foreach (string s in cst.FormElementNames) { AgentSettings[s] = cst.FormElementValues[count]; count++; } return(true); }
///////////////////////////////////////////////////// // // // LoadAgentSettings() // // // ///////////////////////////////////////////////////// //Description: Loads settings from the agent config file. // //Returns: true if successful ////////////////////////////////////////////////////// private unsafe bool LoadAgentSettings(ref Dictionary <string, string> AgentSettings) { //use XML settings file in current directory - "CwAgentConfiguration.xml" //this will allow us to deserialize the XML data into class structures CwXML xml = new CwXML(); CwXML.CodewordSettingsTemplate cst = new CwXML.CodewordSettingsTemplate(); try { cst = xml.LoadSettingsXML("CwAgentConfiguration.xml"); } catch (Exception e) { AgentServiceLog.AppendLine("ERROR: " + e.Message); AgentServiceLog.AppendLine("ERROR: Failed to load settings, terminating..."); return(false); } //copy the settings from the CST object to a more usable dictionary<> struct int count = 0; foreach (string s in cst.FormElementNames) { AgentSettings[s] = cst.FormElementValues[count]; count++; } AgentServiceLog.AppendLine("INITIALIZE: Success."); return(true); }
///////////////////////////////////////////////////// // // // ExpandRegistryKey() // // // ///////////////////////////////////////////////////// //Description: Takes a registry key string and replaces // {<var>} with expanded value //Returns: an array of expanded registry key strings or // null if invalid value passed in ///////////////////////////////////////////////////// internal string[] ExpandRegistryKey(string key, CwXML.RegistryGuidSignature[] GuidSignatures) { int varBegin = key.IndexOf("{<"); int varEnd = key.IndexOf(">}"); //there's no opening tag if (varBegin < 0) { //and no closing tag, so no prob if (varEnd < 0) return new string[] { key }; //er..there was a closing tag but no opening tag.. else return null; //remove malformed indicator entry } //there's an opening tag but no closing tag if (varEnd < 0) return null; //remove malformed indicator entry string var = key.Substring(varBegin + 2, varEnd - (varBegin + 2)); RegistryHelperLog.AppendLine("SCAN: Expanding registry key '" + key + "'..."); //EXPAND GUID if (var == "GUID") { int count = 0; //if there are no static GUIDs to replace {<GUID>} with, return if (GuidSignatures.Length == 0) { RegistryHelperLog.AppendLine("SCAN: No GUIDs were supplied, skipping this indicator check.."); return null; } string[] expandedKeys = new string[GuidSignatures.Length]; string tmpKey = key; //loop through static GUIDs we have and insert into given registry key string foreach (CwXML.RegistryGuidSignature sig in GuidSignatures) { //only consider static GUIDs if (sig.GuidType == "Dynamic") continue; //replace {<GUID>} with this GUID tmpKey = key.Replace("<GUID>", sig.GuidValue); RegistryHelperLog.AppendLine("SCAN: Created expanded registry indicator '" + tmpKey + "'."); expandedKeys[count] = tmpKey; count++; } return expandedKeys; } //EXPAND SID else if (var == "SID") { RegistryHelperLog.AppendLine("SCAN: Expanding SIDs..."); //we need to get a list of user account SIDs using WMI, //loop through that list, and generate new registry //indicator checks for each account SID SelectQuery sQuery = new SelectQuery("Win32_UserAccount", "Domain='" + Environment.UserDomainName + "'"); ManagementObjectSearcher mSearcher = new ManagementObjectSearcher(sQuery); //start inserting new indicators at the end of the list int count = 0; string[] expandedKeys = new string[mSearcher.Get().Count]; //loop through all user accounts and create new indicator foreach (ManagementObject mObject in mSearcher.Get()) { string SID = mObject["SID"].ToString(); string UserName = mObject["Name"].ToString(); string thisExpandedKey = key; //replace var in this expanded key with current SID thisExpandedKey = thisExpandedKey.Replace("{<SID>}", SID); RegistryHelperLog.AppendLine("SCAN: Created expanded registry indicator '" + thisExpandedKey + "'..."); expandedKeys[count] = thisExpandedKey; count++; } RegistryHelperLog.AppendLine("SCAN: Successfully created " + count + " new registry indicators."); return expandedKeys; } else { RegistryHelperLog.AppendLine("ERROR: Invalid embedded variable '" + var + "' in key '" + key + "', skipping this registry indicator."); return null; } }
///////////////////////////////////////////////////// // // // SendResponse() // // // ///////////////////////////////////////////////////// //Description: Attempts to send a message over SSL. // //Throws: Serialization error // //Returns: true if successful ///////////////////////////////////////////////////// internal bool SendResponse(CwXML.CodewordAgentResponse response) { //create an XML serialization object to prepare the command XmlSerializer serializer = new XmlSerializer(typeof(CwXML.CodewordAgentResponse)); //create a memory stream to which we will serialize our response object MemoryStream memStream = new MemoryStream(); //store the object's state in XML format into the memory stream store serializer.Serialize(memStream, response); //tack on "<EOF>" to the message, so the server knows when to stop reading the socket char[] eofbuf = new char[] { '<', 'E', 'O', 'F', '>' }; memStream.Write(Encoding.UTF8.GetBytes(eofbuf), 0, eofbuf.Length); //try to send the raw bytes in the memory stream to the remote socket try { //result of 5 hour debug session: dont use memstream.getBuffer() //it returns all bytes in the buffer, not only the ones used..use ToArray() instead!! CurrentSslStream.Write(memStream.ToArray()); CurrentSslStream.Flush(); } catch (Exception ex) { throw new Exception("sslStream.Write() exception: " + ex.Message); } //DEBUG /* StreamWriter sw = new StreamWriter("ResponseSent.xml"); sw.Write(new string(Encoding.UTF8.GetChars(memStream.ToArray()))); sw.Close(); */ try { memStream.Close(); } catch (Exception) { } return true; }
///////////////////////////////////////////////////// // // // ExecuteCommand() // // // ///////////////////////////////////////////////////// //Description: Executes the given command and returns // a response object. // //Returns: A response code, one of the following: // RESPONSE_EXITING - connection closing // RESPONSE_OK - command completed successfully // RESPONSE_FAIL = command failed ///////////////////////////////////////////////////// internal CwXML.CodewordAgentResponse ExecuteCommand(CwXML.CodewordAgentCommand command) { CwXML.CodewordAgentResponse response = new CwXML.CodewordAgentResponse(); response.CommandReceiveDate = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); response.CommandProcessingStartDate = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); response.ResponseCode = CwConstants.AGENTRESPONSE_OK; response.CommandCodeReceived = command.CommandCode; //------------------------------------------------------------- // // GET SYSTEM INFORMATION // //------------------------------------------------------------- #region GET SYSTEM INFORMATION if (command.CommandCode == CwConstants.AGENTCMD_GETSYSTEMINFO) { response.ResponseInfo = "System information retrieved."; CwXML.CodewordSystemInformation sysinfo = new CwXML.CodewordSystemInformation(); sysinfo.HostInformation = new CwXML.HostInformation(); sysinfo.AgentInformation = new CwXML.AgentInformation(); //host info sysinfo.HostInformation.AgentCurrentDirectory = Environment.CurrentDirectory; sysinfo.HostInformation.MachineName = Environment.MachineName; sysinfo.HostInformation.NumProcessors = Environment.ProcessorCount.ToString(); sysinfo.HostInformation.OSVersionShort = Environment.OSVersion.VersionString; sysinfo.HostInformation.LogicalDrives = string.Join(",", Environment.GetLogicalDrives()); sysinfo.HostInformation.IPAddresses = string.Join(",", AgentScanner.EnvironmentHelper.GetIPAddresses()); sysinfo.HostInformation.OSVersionLong = AgentScanner.EnvironmentHelper.GetOSName(); sysinfo.HostInformation.UserDomainName = Environment.UserDomainName; sysinfo.HostInformation.UserName = Environment.UserName; sysinfo.HostInformation.WorkingSetSize = (Environment.WorkingSet / 1000000).ToString() + "MB"; //agent info sysinfo.AgentInformation.Version = Assembly.GetExecutingAssembly().GetName().ToString(); //use XML settings file in current directory - "CwAgentConfiguration.xml" //this will allow us to deserialize the XML data into class structures CwXML xml = new CwXML(); CwXML.CodewordSettingsTemplate cst = new CwXML.CodewordSettingsTemplate(); try { cst = xml.LoadSettingsXML("CwAgentConfiguration.xml"); } catch (Exception e) { response.ResponseInfo = "There was an error retrieving the agent settings: " + e.Message; } sysinfo.AgentInformation.AgentSettings = cst; //use XML signatures file in current directory - "CwAgentSignatures.xml" //this will allow us to deserialize the XML data into class structures xml = new CwXML(); CwXML.CodewordSignatureTemplate sigs = new CwXML.CodewordSignatureTemplate(); try { sigs = xml.ImportSignatureTemplate("CwAgentSignatures.xml"); } catch (Exception e) { response.ResponseInfo = "There was an error retrieving the agent signatures: " + e.Message; } sysinfo.AgentInformation.AgentSignatures = sigs; //assign sysinfo object to return response response.ResponseSystemInformation = sysinfo; } #endregion //------------------------------------------------------------- // // DOWNLOAD EVIDENCE FILES (COLLECT) // //------------------------------------------------------------- #region DOWNLOAD EVIDENCE FILES (COLLECT) else if (command.CommandCode == CwConstants.AGENTCMD_COLLECT) { //=================================================================== // SEND INTERMEDIATE RESPONSE TO TELL HOST TO START RECEIVING FILES //=================================================================== //send a response then prepare to send WriteConnectionLog("CONNECT: Got command to send evidence files..."); CwXML.CodewordAgentResponse response2 = new CwXML.CodewordAgentResponse(); response2.CommandReceiveDate = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); response2.CommandProcessingStartDate = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); response2.ResponseCode = CwConstants.AGENTRESPONSE_OK_RECVFILE; response2.CommandCodeReceived = command.CommandCode; WriteConnectionLog("CONNECT: Sending response..."); //send response try { SendResponse(response2); } catch (Exception ex) { WriteConnectionLog("Failed to send response in preparation for evidence collection: " + ex.Message); response.ResponseCode = CwConstants.AGENTRESPONSE_FAIL; response.ResponseInfo = "Failed to send response in preparation for evidence collection: " + ex.Message; return response; } WriteConnectionLog("CONNECT: Sending files.."); //=================================================================== // SEND EVIDENCE FILES //=================================================================== //get list of files tos end CwXML.FileSignatureMatch[] fileSigsToSend = command.CommandCollectOrMitigationTask.SignatureMatches.FileSignatureMatches; int count = 0; //send the files foreach (CwXML.FileSignatureMatch match in fileSigsToSend) { try { SendBinaryFile(match.FullPath); } catch (Exception ex) { response.ResponseCode = CwConstants.AGENTRESPONSE_FAIL; response.ResponseInfo = "Failed to send binary file '" + match.FullPath + "': " + ex.Message; break; } count++; } if (response.ResponseCode == CwConstants.AGENTRESPONSE_OK) response.ResponseInfo = "Successfully sent " + count + " evidence files."; } #endregion //------------------------------------------------------------- // // PERFORM MITIGATION TASK // //------------------------------------------------------------- #region PERFORM MITIGATION TASK else if (command.CommandCode == CwConstants.AGENTCMD_MITIGATE) { //the mitigation task is stored in the command object as an anomaly report CwXML.CodewordAgentAnomalyReport MitigationTask = command.CommandCollectOrMitigationTask; if (MitigationTask != null) { CwXML.CodewordAgentSignatureMatches matches = MitigationTask.SignatureMatches; //mitigate registry items if (matches.RegistrySignatureMatches != null) { if (matches.RegistrySignatureMatches.Length > 0) { CwXML.RegistrySignatureMatch[] regMatches = matches.RegistrySignatureMatches; AgentScanner.RegistryHelper RegistryScanner = new AgentScanner.RegistryHelper(); RegistryScanner.LoadNtUserDatFiles(false); RegistryScanner.CleanRegistryFindings(ref regMatches, false); RegistryScanner.LoadNtUserDatFiles(true); response.ResponseLog = RegistryScanner.RegistryHelperLog.ToString(); //assign the matches back to our main object, so the ActionSuccessful variable gets sent back matches.RegistrySignatureMatches = regMatches; } } //mitigate file items if (matches.FileSignatureMatches != null) { if (matches.FileSignatureMatches.Length > 0) { CwXML.FileSignatureMatch[] fileMatches = matches.FileSignatureMatches; AgentScanner.FileHelper FileScanner = new AgentScanner.FileHelper(); FileScanner.CleanFileFindings(ref fileMatches); response.ResponseLog = FileScanner.FileHelperLog.ToString(); //assign the matches back to our main object, so the ActionSuccessful variable gets sent back matches.FileSignatureMatches = fileMatches; } } //mitigate memory items if (matches.MemorySignatureMatches != null) { if (matches.MemorySignatureMatches.Length > 0) { CwXML.MemorySignatureMatch[] memMatches = matches.MemorySignatureMatches; AgentScanner.MemoryHelper MemoryScanner = new AgentScanner.MemoryHelper(); MemoryScanner.CleanMemoryFindings(ref memMatches); response.ResponseLog = MemoryScanner.MemoryHelperLog.ToString(); //assign the matches back to our main object, so the ActionSuccessful variable gets sent back matches.MemorySignatureMatches = memMatches; } } //assign the main object to the response's anomaly report response.ResponseAnomalyReport = new CwXML.CodewordAgentAnomalyReport(); response.ResponseAnomalyReport.SignatureMatches = matches; } else { response.ResponseInfo = "Error completing mitigation task: the mitigation object was null!"; response.ResponseCode = CwConstants.AGENTRESPONSE_FAIL; } } #endregion //------------------------------------------------------------- // // START A NEW SCAN // //------------------------------------------------------------- #region START A NEW SCAN else if (command.CommandCode == CwConstants.AGENTCMD_STARTSCAN) { //ENTERPRISE MODE CHECK: //make sure there isnt an already-completed scan from //starting up in enterprise mode. if (EnterpriseModeScanLog != null) { response.ResponseLog = EnterpriseModeScanLog.ToString(); response.ResponseInfo = "These results are from a previous scan issued during agent startup. To run a new scan, please re-issue the scan command."; //clear the enterprise scan results EnterpriseModeScanLog = null; } //otherwise, issue a completely new scan task //warning: this can be a lengthy operation (> 10 min) else { CwXML.CodewordAgentAnomalyReport anomalyReport = new CwXML.CodewordAgentAnomalyReport(); AgentScanner scanner = new AgentScanner(); StringBuilder scannerLog = new StringBuilder(); try { scannerLog = scanner.StartScanTask(ref anomalyReport); } catch (Exception ex) { StreamWriter sw = new StreamWriter("AgentScanLog.txt", false); sw.WriteLine(ex.Message); sw.WriteLine(""); if (AgentScanner.AgentScanLog != null) sw.WriteLine(AgentScanner.AgentScanLog.ToString()); sw.Close(); } if (scannerLog != null && anomalyReport != null) { response.ResponseAnomalyReport = anomalyReport; //invalid xml chars replaced in AgentScanner.StartScanTask() response.ResponseInfo = "Scan complete."; response.ResponseLog = CwXML.ReplaceInvalidXmlChars(scannerLog.ToString()); } else { response.ResponseCode = CwConstants.AGENTRESPONSE_FAIL; response.ResponseInfo = "An unrecoverable error occured during the scan."; } } } #endregion //------------------------------------------------------------- // // EXIT, NO MORE COMMANDS // //------------------------------------------------------------- #region EXIT else if (command.CommandCode == CwConstants.AGENTCMD_EXIT || command.CommandCode == CwConstants.AGENTCMD_NOMORECOMMANDS) { // //NO ACTION REQUIRED // } #endregion //------------------------------------------------------------- // // INVALID COMMAND // //------------------------------------------------------------- #region INVALID COMMAND else if (command.CommandCode == CwConstants.AGENTCMD_UNKNOWN) { response.ResponseCode = CwConstants.AGENTRESPONSE_FAIL; //the error is stored in this member of the fake command we created earlier response.ResponseInfo = string.Join(",", command.CommandParameters); } #endregion //------------------------------------------------------------- // // RECEIVE NEW SIGNATURE UPDATE FILE // //------------------------------------------------------------- #region RECEIVE NEW SIGNATURE UPDATE FILE else if (command.CommandCode == CwConstants.AGENTCMD_UPDATESIG) { //send a response then prepare to receive WriteConnectionLog("CONNECT: Got command to download new signature file..."); CwXML.CodewordAgentResponse response2 = new CwXML.CodewordAgentResponse(); response2.CommandReceiveDate = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); response2.CommandProcessingStartDate = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); response2.ResponseCode = CwConstants.AGENTRESPONSE_OK_SENDFILE; response2.CommandCodeReceived = command.CommandCode; WriteConnectionLog("CONNECT: Sending response..."); //send response try { SendResponse(response2); } catch (Exception ex) { WriteConnectionLog("Failed to send response in preparation for file retrieval: " + ex.Message); response.ResponseCode = CwConstants.AGENTRESPONSE_FAIL; response.ResponseInfo = "Failed to send response in preparation for file retrieval: " + ex.Message; return response; } byte[] filedata; WriteConnectionLog("CONNECT: Waiting for file..."); //receive the file try { filedata = ReceiveFile(); } catch (Exception ex) { WriteConnectionLog("Failed to receive file contents: " + ex.Message); response.ResponseCode = CwConstants.AGENTRESPONSE_FAIL; response.ResponseInfo = "Failed to receive file contents: " + ex.Message; return response; } WriteConnectionLog("CONNECT: File retrieved, saving locally..."); //overwrite our current XML signature file try { if (File.Exists("CwAgentSignatures.xml")) File.Delete("CwAgentSignatures.xml"); AgentScanner.EnvironmentHelper.BinaryWrite("CwAgentSignatures.XML", filedata); } catch (Exception ex) { WriteConnectionLog("Failed to write new signature file: " + ex.Message); response.ResponseCode = CwConstants.AGENTRESPONSE_FAIL; response.ResponseInfo = "Failed to write new signature file: " + ex.Message; return response; } //success! response.ResponseInfo = "Successfully updated signatures file."; } #endregion response.CommandProcessingEndDate = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); return response; }
///////////////////////////////////////////////////// // // // ReceiveFiles() // // // ///////////////////////////////////////////////////// //Description: Receives as many files as the agent sends. // Saves them to the specified folder. // //Throws: read error // //Returns: the binary data ///////////////////////////////////////////////////// internal bool ReceiveFiles(string SaveToFolder, CwXML.FileSignatureMatch[] collectionTargets) { MemoryStream ms = new MemoryStream(); Decoder UTF8Decoder = Encoding.UTF8.GetDecoder(); int numBytesRead = -1; byte[] fileDataBuffer; //loop through all files in the collection target and download them foreach(CwXML.FileSignatureMatch fileMatch in collectionTargets) { string fileNameToCollect = fileMatch.FileName; string filePathToCollect = fileMatch.FullPath; long fileSizeToCollect = fileMatch.FileSize; string fileHashToCollect = fileMatch.FileHash; string fileHashTypeToCollect = fileMatch.FileHashType; //create a new buffer to store this file - size is the file's size fileDataBuffer = new byte[fileSizeToCollect]; //==================================== //DOWNLOAD FILE BYTES //==================================== try { //store a max of 2048 bytes starting at offset 0 in the buffer array numBytesRead = ClientSslStream.Read(fileDataBuffer, 0, (int)fileSizeToCollect); } catch (IOException ex) { throw new Exception("ReceiveFiles(): Caught IO Exception (read " + numBytesRead.ToString() + " bytes): " + ex.Message); } catch (Exception ex) { throw new Exception("ReceiveFiles(): Caught other exception (read " + numBytesRead.ToString() + " bytes): " + ex.Message); } //throw an error if the file was corrupted if (numBytesRead != fileSizeToCollect) throw new Exception("ReceiveFiles(): The downloaded file size (" + numBytesRead.ToString() + ") does not match the expected file size (" + fileSizeToCollect.ToString() + ")!"); //do a binary write to save this file to the target folder string outputFullPath = SaveToFolder + "\\" + fileNameToCollect; StreamWriter sw = new StreamWriter(outputFullPath); BinaryWriter bw = new BinaryWriter(sw.BaseStream); bw.Write(fileDataBuffer); bw.Flush(); bw.Close(); //MATCH MD-5 HASH IF SPECIFIED if (fileHashTypeToCollect == "MD5" && fileHashToCollect != "") { string thisMD5=GetMD5HashOfFile(outputFullPath); if (thisMD5 != fileHashToCollect) throw new Exception("ReceiveFiles(): The MD5 hash of the download evidence file (" + thisMD5.ToString() + ") does not match the expected MD5 hash (" + fileHashToCollect.ToString() + ")!"); } //MATCH SHA-1 HASH IF SPECIFIED else if (fileHashTypeToCollect == "SHA1" && fileHashToCollect != "") { string thisSHA1 = GetSHA1HashOfFile(outputFullPath); if (thisSHA1 != fileHashToCollect) throw new Exception("ReceiveFiles(): The SHA1 hash of the download evidence file (" + thisSHA1.ToString() + ") does not match the expected SHA1 hash (" + fileHashToCollect.ToString() + ")!"); } } return true; }
///////////////////////////////////////////////////// // // // FindAndUpdateMatchRecord() // // // ///////////////////////////////////////////////////// //Description: Updates the signature findings listviews // with the result of a mitigation operation // //Returns: void ///////////////////////////////////////////////////// private void FindAndUpdateMatchRecords(CwXML.CodewordAgentSignatureMatches updatedMatchRecords) { //-------------------------------------- // REGISTRY //-------------------------------------- if (updatedMatchRecords.RegistrySignatureMatches != null) { CwXML.RegistrySignatureMatch[] regMatches = updatedMatchRecords.RegistrySignatureMatches; //loop through all registry signature matches that were mitigated, trying to find a //corresponding signature match in our old global var. foreach (CwXML.RegistrySignatureMatch rNew in regMatches) { int matchIndex = 0; //loop through all the signature matches currently stored in our global var. foreach (CwXML.RegistrySignatureMatch rOld in LastAnomalyReport.SignatureMatches.RegistrySignatureMatches) { if (rOld.RegistryKeyName == rNew.RegistryKeyName && rOld.RegistryValueName == rNew.RegistryValueName && rOld.Action == rNew.Action) { LastAnomalyReport.SignatureMatches.RegistrySignatureMatches[matchIndex].ActionSuccessful = rNew.ActionSuccessful; break; } matchIndex++; } } } //-------------------------------------- // FILE //-------------------------------------- if (updatedMatchRecords.FileSignatureMatches != null) { CwXML.FileSignatureMatch[] fileMatches = updatedMatchRecords.FileSignatureMatches; //loop through all file signature matches that were mitigated, trying to find a //corresponding signature match in our old global var. foreach (CwXML.FileSignatureMatch fNew in fileMatches) { int matchIndex = 0; //loop through all the signature matches currently stored in our global var. foreach (CwXML.FileSignatureMatch fOld in LastAnomalyReport.SignatureMatches.FileSignatureMatches) { if (fOld.FullPath == fNew.FullPath && fOld.FileSize == fNew.FileSize && fOld.FileHash == fNew.FileHash && fOld.Action == fNew.Action) { LastAnomalyReport.SignatureMatches.FileSignatureMatches[matchIndex].ActionSuccessful = fNew.ActionSuccessful; break; } matchIndex++; } } } //-------------------------------------- // MEMORY //-------------------------------------- if (updatedMatchRecords.MemorySignatureMatches != null) { CwXML.MemorySignatureMatch[] memMatches = updatedMatchRecords.MemorySignatureMatches; //loop through all memory signature matches that were mitigated, trying to find a //corresponding signature match in our old global var. foreach (CwXML.MemorySignatureMatch mNew in memMatches) { int matchIndex = 0; //loop through all the signature matches currently stored in our global var. foreach (CwXML.MemorySignatureMatch mOld in LastAnomalyReport.SignatureMatches.MemorySignatureMatches) { if (mOld.ProcessId == mNew.ProcessId && mOld.ProcessName == mNew.ProcessName) { LastAnomalyReport.SignatureMatches.MemorySignatureMatches[matchIndex].ActionSuccessful = mNew.ActionSuccessful; break; } matchIndex++; } } } }
///////////////////////////////////////////////////// // // // ScanForRegistrySignatures() // // // ///////////////////////////////////////////////////// //Description: Scans the registry for the given // signatures, storing any findings in // the passed-in matches structure. //Returns: none ///////////////////////////////////////////////////// internal void ScanForRegistrySignatures(CwXML.RegistrySignature[] signatures, CwXML.RegistryGuidSignature[] guidSignatures, ref CwXML.RegistrySignatureMatch[] matches) { string keyName, valueName, valueData, action, hive, keyWithoutHive; int firstSlashInKeyName = 0; RegistryKey key; Regex filePathValidator = new Regex(@"^(([a-zA-Z]\:)|(\\))(\\{1}|((\\{1})[^\\]([^/:*?<>""|]*))+)$"); string[] expandedKeys; ArrayList matchList = new ArrayList(); //loop through all registry indicators - admin and user foreach (CwXML.RegistrySignature signature in signatures) { //extract values from object keyName = signature.KeyName; valueName = signature.ValueName; valueData = signature.ValueData; action = signature.Action; firstSlashInKeyName = keyName.IndexOf('\\'); hive = keyName.Substring(0, firstSlashInKeyName); keyWithoutHive = keyName.Substring(firstSlashInKeyName, keyName.Length - firstSlashInKeyName); if (hive == "HKLM") key = Registry.LocalMachine; else if (hive == "HKCR") key = Registry.ClassesRoot; else if (hive == "HKU") key = Registry.Users; else if (hive == "HKCC") key = Registry.CurrentConfig; else { RegistryHelperLog.AppendLine("WARNING: Invalid hive detected in registry indicator:"); RegistryHelperLog.AppendLine(" Key: '" + keyName + "'"); RegistryHelperLog.AppendLine(" Parsed hive: '" + hive + "'"); RegistryHelperLog.AppendLine("WARNING: Skipping this indicator..."); continue; //skip if bad hive } RegistryHelperLog.AppendLine("SCAN: Using hive '" + hive + "'."); //expand any {<var>} expandable values in registry key string expandedKeys = ExpandRegistryKey(keyWithoutHive, guidSignatures); //only returns null if there was a malformed var, so skip that check if (expandedKeys == null) continue; //loop through the record/checks we built, or didn't build - if there was no var //to expand, we just got the same record back. foreach (string expandedKey in expandedKeys) { if (expandedKey == null) continue; //remove leading and trailing slashes keyName = expandedKey.Trim(new char[] { ' ', '\\' }); RegistryHelperLog.AppendLine("SCAN: Scanning for signature '" + hive + "\\" + keyName + "\\" + valueName + "'..."); RegistryKey parentKey = key.OpenSubKey(keyName, true); //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // SIGNATURE HIT //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //if the key exists, we have a match. if (parentKey != null) { try { ArrayList valnames = new ArrayList(); //build an arraylist of value names to look for //if the valueName supplied in the signature is empty, we will copy //all valueName's in this registry key. if (valueName == "" || valueName == null) { foreach (string valname in parentKey.GetValueNames()) valnames.Add(valname); //if there are no value names, add the (Default) one, represented by "" in .net if (valnames.Count == 0) valnames.Add(""); } else { //otherwise we just want a single value name underneath this key valnames.Add(valueName); } //loop thru all value names to look for and add a new match record foreach (string v in valnames) { //get the value name object obj = parentKey.GetValue(v); //parse the value data - binary, expand string, etc... if (obj != null) { string displayData = ""; string valdata = ""; RegistryHelperLog.AppendLine("SCAN: Signature matched on host!"); //create a new RegistrySignatureMatch object CwXML.RegistrySignatureMatch matchRecord = new CwXML.RegistrySignatureMatch(); matchRecord.RegistryKeyName = hive+"\\"+keyName; //the expanded registry key name plus hive matchRecord.RegistryValueName = v; matchRecord.Action = action; //get the value data of this value name based on type if (parentKey.GetValueKind(v) == RegistryValueKind.Binary) { StringBuilder str = GetEncodingsFromBinaryRegData((byte[])parentKey.GetValue(v)); valdata = str.ToString(); displayData = valdata; } //handle DWORD and QWORD reg data else if (parentKey.GetValueKind(v) == RegistryValueKind.DWord || parentKey.GetValueKind(v) == RegistryValueKind.QWord) { valdata = parentKey.GetValue(v).ToString(); //first value will be in decimal, hex in parenthesis displayData = "'" + int.Parse(valdata).ToString() + "' (0x" + int.Parse(valdata).ToString("x") + ")"; } else { valdata = parentKey.GetValue(v).ToString(); displayData = "'" + valdata + "'"; } matchRecord.RegistryValueData = valdata; //if it's a file, mark the appropriate field in the registry signature entry. //later on , these files will be deleted if settings dictate it. if (filePathValidator.IsMatch(valdata)) matchRecord.IsFileOnDisk = true; else matchRecord.IsFileOnDisk = false; //log it in pretty format RegistryHelperLog.AppendLine(" " + v + " = '" + displayData + "'"); //add it to our result set matchList.Add(matchRecord); } //otherwise teh value name coudl not be retrieved, so no real match here. else { RegistryHelperLog.AppendLine("SCAN: The value name '" + keyName + "\\" + v + "' doesn't exist."); } } } catch (Exception e) { RegistryHelperLog.AppendLine("SCAN: Caught exception '" + e.Message + "', can't get this value name."); } } else { RegistryHelperLog.AppendLine("SCAN: The parent key '" + keyName + "' doesn't exist."); } } //end looping through expanded reg values for this reg indicator } // end looping through ALL registry indicators //re-initialize our return object to the number of items in arraylist int i = 0; matches = new CwXML.RegistrySignatureMatch[matchList.Count]; //add all registry findings in the arraylist to our matches object foreach (CwXML.RegistrySignatureMatch match in matchList) { matches[i] = new CwXML.RegistrySignatureMatch(); matches[i] = match; i++; } }
///////////////////////////////////////////////////// // // // LoadAgentSettings() // // // ///////////////////////////////////////////////////// //Description: Loads settings from the agent config file. // //Returns: true if successful ////////////////////////////////////////////////////// private unsafe bool LoadAgentSettings(ref Dictionary<string, string> AgentSettings) { //use XML settings file in current directory - "CwAgentConfiguration.xml" //this will allow us to deserialize the XML data into class structures CwXML xml = new CwXML(); CwXML.CodewordSettingsTemplate cst = new CwXML.CodewordSettingsTemplate(); try { cst = xml.LoadSettingsXML("CwAgentConfiguration.xml"); } catch (Exception e) { AgentServiceLog.AppendLine("ERROR: " + e.Message); AgentServiceLog.AppendLine("ERROR: Failed to load settings, terminating..."); return false; } //copy the settings from the CST object to a more usable dictionary<> struct int count = 0; foreach (string s in cst.FormElementNames) { AgentSettings[s] = cst.FormElementValues[count]; count++; } AgentServiceLog.AppendLine("INITIALIZE: Success."); return true; }
///////////////////////////////////////////////////// // // // CleanMemoryFindings() // // // ///////////////////////////////////////////////////// //Description: kills/suspends offending processes //Returns: none ///////////////////////////////////////////////////// internal void CleanMemoryFindings(ref CwXML.MemorySignatureMatch[] MemorySignatureMatches) { //the line below is added for compatibility when calling this function //over remote channels from the admin console if (MemoryHelperLog == null) MemoryHelperLog = new StringBuilder(); int count = 0; //Possible actions: /* Terminate process if exists Terminate process if keyword found Suspend containing thread if keyword found */ foreach (CwXML.MemorySignatureMatch match in MemorySignatureMatches) { string action = match.Action; IntPtr hProcess = Win32Helper.OpenProcess(Win32Helper.PROCESS_TERMINATE, false, match.ProcessId); //try to obtain a handle to the process to kill/suspend if(hProcess == IntPtr.Zero) { MemoryHelperLog.AppendLine("CLEAN: Failed to open process PID " + match.ProcessId.ToString() + ": " + Win32Helper.GetLastError32()); MemorySignatureMatches[count].ActionSuccessful = false; count++; continue; } // * TERMINATE PROCESS * if (action == "Terminate process if exists" || action == "Terminate process if keyword found") { //note: experience has shown you cant trust the return value of ZwTerminateProcess(). //dont even bother to validate it, assume success (we do have elevated privs!) Win32Helper.ZwTerminateProcess(hProcess, Win32Helper.STATUS_SUCCESS); Win32Helper.CloseHandle(hProcess); MemorySignatureMatches[count].ActionSuccessful = true; } //* SUSPEND CONTAINING THREAD * else if (action == "Suspend containing thread if keyword found") { // //TODO // MemoryHelperLog.AppendLine("CLEAN: Warning: Suspending thread not implemented."); MemorySignatureMatches[count].ActionSuccessful = false; } count++; } return; }
private void loadSettingsToolStripMenuItem_Click(object sender, EventArgs e) { //----------------------------------------------- // DETERMINE IMPORT FILENAME //----------------------------------------------- //show browse dialog to select file OpenFileDialog dlg = new OpenFileDialog(); dlg.CheckFileExists = true; dlg.CheckPathExists = true; dlg.DefaultExt = ".xml"; //default extension dlg.Title = "Select settings file"; dlg.Filter = "XML Files|*.xml"; dlg.Multiselect = false; CwXML xml = new CwXML(); CwXML.CodewordSettingsTemplate cst=new CwXML.CodewordSettingsTemplate(); //the user clicked cancel if (dlg.ShowDialog() != DialogResult.OK) return; string filename = dlg.FileName; try { cst=xml.LoadSettingsXML(filename); } catch (Exception ex) { MessageBox.Show(ex.Message); return; } //we successfully deserialized the XML document, now load it into //the GUI form at the appropriate locations // int numInvalid = 0, numSuccess = 0, count = 0; // //loop through form element names stored in the settings document //and try to set them in the Window's form. // foreach (string elementName in cst.FormElementNames) { //if we looped through all tab pages and didnt find this element name, //it must be deprecated - store for error msg later if (!((bool)CrawlTabPages("SetFormValue", elementName, cst.FormElementValues[count]))) numInvalid++; else numSuccess++; count++; } MessageBox.Show("Successfully loaded " + numSuccess + " settings!\n\nThere were " + numInvalid + " invalid setting items."); }
///////////////////////////////////////////////////// // // // PrintMemoryFindings() // // // ///////////////////////////////////////////////////// //Description: outputs memory findings //Returns: none ///////////////////////////////////////////////////// internal void PrintMemoryFindings(CwXML.MemorySignatureMatch[] MemorySignatureMatches, ref StringBuilder output) { output.AppendLine(""); output.AppendLine("REPORT: ******************************"); output.AppendLine("REPORT: Memory Findings"); output.AppendLine("REPORT: ******************************"); output.AppendLine(""); output.AppendLine("Process\t\tPID (PPID)\t\tKeywords\t\tMatching Block\t\tChild ThreadIds\t\tMalicious Module(s)\t\tSuspicious Heap Blockp\t\tAction\t\tAction Successful\t"); if (MemorySignatureMatches.Length == 0) { output.AppendLine("REPORT: No memory signatures were found."); } else { //loop through all match records foreach (CwXML.MemorySignatureMatch match in MemorySignatureMatches) { output.AppendLine(""); output.Append(match.ProcessName + "\t\t"); output.Append(match.ProcessId.ToString() + "("+match.ParentProcessId.ToString()+"\t\t"); output.Append(match.Keywords + "\t\t"); output.Append(match.MatchingBlock + "\t\t"); output.Append(match.ChildThreadIds + "\t\t"); output.Append(match.MaliciousLoadedModuleName + ","+match.MaliciousLoadedModuleSize+" bytes,"+match.MaliciousLoadedModuleBaseAddr+" base addr\t\t"); output.Append(match.SuspiciousHeapBlockRange + "\t\t"); output.Append(match.Action + "\t\t"); output.Append(match.ActionSuccessful.ToString() + "\t\t"); } } output.AppendLine(""); output.AppendLine("REPORT: ******************************"); output.AppendLine(""); }
///////////////////////////////////////////////////// // // // ScanForMemorySignatures() // // // ///////////////////////////////////////////////////// //Description: this function searches the heap space of // process(es) of interest for supplied keyword(s) //Returns: true if keyword found ///////////////////////////////////////////////////// internal unsafe void ScanForMemorySignatures(CwXML.RegistrySignatureMatch[] RegistryFindings, CwXML.MemorySignature[] MemorySignatures, ref CwXML.MemorySignatureMatch[] matches, bool SearchCmdline, bool SearchHeap, bool searchModuleList, bool searchRegistryFindings) { //we will use this regex to validate whether a given keyword is a valid filename/path or not //this is used when we are walking the module list Regex filePathValidator = new Regex(@"^(([a-zA-Z]\:)|(\\))(\\{1}|((\\{1})[^\\]([^/:*?<>""|]*))+)$"); ArrayList Findings=new ArrayList(); // //loop through each memory signature and search // foreach (CwXML.MemorySignature m in MemorySignatures) { //is this process name for this signature in the list of active processes? string ProcessName = m.ProcessName; ArrayList ProcessInfo = GetActiveProcessInfo(ProcessName); string action = m.Action; //if not, skip it. if (ProcessInfo == null) { MemoryHelperLog.AppendLine("SCAN: The target process " + ProcessName + " is not running, skipping this signature..."); continue; } if (ProcessInfo.Count == 0) { MemoryHelperLog.AppendLine("SCAN: The target process " + ProcessName + " did not return a full data set, skipping this signature..."); continue; } //* PROCESS OF INTEREST IS IN THE LIST OF RUNNING PROCESSES... *// uint pid = (uint)ProcessInfo[0]; uint ppid = (uint)ProcessInfo[1]; uint threadCount = (uint)ProcessInfo[2]; //were any keywords given to search this process heap/cmdline/modlist? string[] Keywords = m.Keywords.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); //if no keywords were given, then we can conclude our task was only to identify that //this process name was running on the system. if that's the case, return a hit. if (Keywords.Length == 0) { MemoryHelperLog.AppendLine("SCAN: Match found for process name '" + ProcessName + "'!"); CwXML.MemorySignatureMatch matchRecord = new CwXML.MemorySignatureMatch(); matchRecord.MatchingBlock = ProcessName; matchRecord.ProcessId = pid; matchRecord.ParentProcessId = ppid; matchRecord.ProcessName = ProcessName; matchRecord.Keywords = ""; matchRecord.ChildThreadIds = GetChildThreadIds((uint)pid); matchRecord.Action = action; //note: we are adding the match this way to be consistent with how //we have to add them in SearchProcessCmdLine() and other funcs ArrayList tmpMatches = new ArrayList(); tmpMatches.Add(matchRecord); Findings.Add(tmpMatches); continue; } //otherwise, we need to search either the process heap, cmd line, or module list //for the given keywords in this memory signature. ArrayList KeywordList = new ArrayList(); KeywordList.AddRange(Keywords); //if the user wants to use registry findings in our keyword scan, add them now if (searchRegistryFindings) foreach (CwXML.RegistrySignatureMatch regMatch in RegistryFindings) if (regMatch.RegistryValueData != null) if (regMatch.RegistryValueData.Length < 300) KeywordList.Add(regMatch.RegistryValueData); //********************************* // SCAN OPTIONS //********************************* //perform the following per-process scans, depending on user options: // 1) use WMI to search the Cmdline for this process for keywords/indicators // 2) use Win32 API to search the module list for keywords/indicators // 3) use Win32 API to search the heap space for keywords/indicators MemoryHelperLog.AppendLine("SCAN: Searching target process " + ProcessName + " (PID=" + pid.ToString() + ")..."); MemoryHelperLog.AppendLine("SCAN: Using keyword search list (" + KeywordList.Count + "): '" + string.Join(",", ((string[])KeywordList.ToArray(typeof(string)))) + "'"); //********************************* // PERFORM SCANS //********************************* // 1) use WMI to search the Cmdline for this process for keywords/indicators if (SearchCmdline) { ArrayList cmdlineFindings = new ArrayList(); MemoryHelperLog.Append("SCAN: Searching command line ..."); if (!SearchProcessCmdline(pid, ppid, action, KeywordList, ref cmdlineFindings)) MemoryHelperLog.Append("nothing."); else { MemoryHelperLog.Append(cmdlineFindings.Count + " matches!"); Findings.Add(cmdlineFindings); } MemoryHelperLog.AppendLine(); } // 2) use Win32 API to search the module list for keywords/indicators if (searchModuleList) { ArrayList modListFindings = new ArrayList(); MemoryHelperLog.Append("SCAN: Searching loaded module list ..."); if (!SearchProcessLoadedModuleList(pid, ppid, ProcessName, action, KeywordList, ref modListFindings)) MemoryHelperLog.Append("nothing."); else { MemoryHelperLog.Append(modListFindings.Count + " matches!"); Findings.Add(modListFindings); } MemoryHelperLog.AppendLine(); } // 3) use Win32 API to search the heap space for keywords/indicators if (SearchHeap) { ArrayList heapFindings = new ArrayList(); MemoryHelperLog.AppendLine("SCAN: Searching heap space ..."); if (!SearchProcessHeap((uint)pid, ppid, ProcessName, action, KeywordList, ref heapFindings)) MemoryHelperLog.Append("SCAN: Nothing."); else { MemoryHelperLog.AppendLine("SCAN: "+heapFindings.Count + " matches!"); Findings.Add(heapFindings); } MemoryHelperLog.AppendLine(); } } MemoryHelperLog.AppendLine("SCAN: Done scanning processes, collating results..."); //first find out how many findings we had if (Findings.Count > 0) { MemoryHelperLog.AppendLine("SCAN: There are " + Findings.Count + " memory finding set matches."); int retBufSize = 0, i = 0; foreach (ArrayList ar in Findings) foreach (CwXML.MemorySignatureMatch m in ar) retBufSize++; matches = new CwXML.MemorySignatureMatch[retBufSize]; //loop through all matches and add them to findings foreach (ArrayList ar in Findings) { foreach (CwXML.MemorySignatureMatch m in ar) { matches[i] = new CwXML.MemorySignatureMatch(); matches[i] = m; i++; } } } else matches = new CwXML.MemorySignatureMatch[0]; }
///////////////////////////////////////////////////// // // // StartScanTask() // // // ///////////////////////////////////////////////////// //Description: This function only performs the setup // necessary to perform a scan of registry, // disk and memory, and to report those // results back to the admin console. // //Returns: a stringbuilder object containing log data. ///////////////////////////////////////////////////// internal StringBuilder StartScanTask(ref CwXML.CodewordAgentAnomalyReport anomalyReport) { //clear any existing results anomalyReport = new CwXML.CodewordAgentAnomalyReport(); AgentHeuristicMatches = new CwXML.CodewordAgentHeuristicMatches(); AgentHeuristicMatches.KernelModeMatches = new CwXML.KernelModeHeuristicMatches(); AgentHeuristicMatches.UserModeMatches = new CwXML.UserModeHeuristicMatches(); AgentSignatureMatches = new CwXML.CodewordAgentSignatureMatches(); AgentSignatureMatches.RegistrySignatureMatches = new CwXML.RegistrySignatureMatch[0]; AgentSignatureMatches.MemorySignatureMatches = new CwXML.MemorySignatureMatch[0]; AgentSignatureMatches.FileSignatureMatches = new CwXML.FileSignatureMatch[0]; // //1. Load settings from XML file extracted to local dir from MSI // AgentScanLog.AppendLine("INITIALIZE: Loading scan settings..."); if (!LoadAgentSettings(ref AgentSettings)) return AgentScanLog; // //2. Load signatures - this only needs to be done once here for the whole file // AgentScanLog.AppendLine("SCAN: Loading signatures from XML file..."); if (!LoadAgentSignatures()) return AgentScanLog; // //3. Disable .NET security // EnvironmentHelper.ToggleDotnetSecurity("Off", "INITIALIZE"); // //4. kick off scan // AgentScanLog.AppendLine("SCAN: Scan starting on " + DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")); DoSignatureScan(); //IMPORTANT: pin the scan results object so the garbage collector doesn't mangle it... //GCHandle gchAgentSignatureMatches = GCHandle.Alloc(AgentSignatureMatches, GCHandleType.Pinned); //only auto-mitigate if option set. if (AgentSettings["Option_AutoMitigate"] == "True") DoMitigate(); DoUserModeHeuristics(); DoKernelModeHeuristics(); AgentScanLog.AppendLine("SCAN: Scan finished on " + DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss")); // //5. re-enable .NET security // EnvironmentHelper.ToggleDotnetSecurity("On", "FINALIZE"); // //6. return our results object byref // //sanitize the XML by escaping invalid characters first int count = 0; foreach (CwXML.RegistrySignatureMatch match in AgentSignatureMatches.RegistrySignatureMatches) { match.RegistryValueData = CwXML.ReplaceInvalidXmlChars(match.RegistryValueData); match.RegistryValueName = CwXML.ReplaceInvalidXmlChars(match.RegistryValueName); AgentSignatureMatches.RegistrySignatureMatches[count] = match; count++; } count = 0; foreach (CwXML.MemorySignatureMatch match in AgentSignatureMatches.MemorySignatureMatches) { //keywords are not required in memory search - could just be looking for presence of a process name if (match.Keywords != null) match.Keywords = CwXML.ReplaceInvalidXmlChars(match.Keywords); match.MatchingBlock = CwXML.ReplaceInvalidXmlChars(match.MatchingBlock); AgentSignatureMatches.MemorySignatureMatches[count] = match; count++; } //assign the fields of the passed-in object byref anomalyReport.SignatureMatches = AgentSignatureMatches; anomalyReport.HeuristicMatches = AgentHeuristicMatches; //release our pinned handle to results //gchAgentSignatureMatches.Free(); return AgentScanLog; }
///////////////////////////////////////////////////// // // // LoadAgentSignatures() // // // ///////////////////////////////////////////////////// //Description: Loads signatures from the agent sig file. // //Returns: true if successful ////////////////////////////////////////////////////// private unsafe bool LoadAgentSignatures() { //use XML signature template file in current directory - "CwAgentSignatures.xml" //this will allow us to deserialize the XML data into class structures CwXML xml = new CwXML(); CwXML.CodewordSignatureTemplate sigs = new CwXML.CodewordSignatureTemplate(); try { sigs = xml.ImportSignatureTemplate("CwAgentSignatures.xml"); } catch (Exception e) { AgentScanLog.AppendLine("ERROR: " + e.Message); AgentScanLog.AppendLine("ERROR: Failed to load signatures, terminating..."); return false; } //save the values into global variables for all funcs to access AgentRegistrySignatures = sigs.RegistrySignatures; AgentRegistryGuidSignatures = sigs.RegistryGuidSignatures; AgentFileSignatures = sigs.FileSignatures; AgentMemorySignatures = sigs.MemorySignatures; return true; }
///////////////////////////////////////////////////// // // // SendCommand() // // // ///////////////////////////////////////////////////// //Description: Attempts to send a message over SSL. // //Throws: Serialization error // //Returns: true if successful ///////////////////////////////////////////////////// internal bool SendCommand(int commandCode, string[] args, int timeout, bool responseRequired, CwXML.CodewordAgentAnomalyReport CollectOrMitigationTask) { CwXML.CodewordAgentCommand cmd = new CwXML.CodewordAgentCommand(); cmd.CommandCode = commandCode; cmd.CommandParameters = args; cmd.CommandTimeout = timeout; cmd.ResponseRequired = responseRequired; cmd.CommandCollectOrMitigationTask = CollectOrMitigationTask; //create an XML serialization object to prepare the command XmlSerializer serializer = new XmlSerializer(typeof(CwXML.CodewordAgentCommand)); //create a memory stream to which we will serialize our response object MemoryStream memStream = new MemoryStream(); //store the object's state in XML format into the memory stream store serializer.Serialize(memStream, cmd); //tack on "<EOF>" to the message, so the server knows when to stop reading the socket char[] eofbuf = new char[] { '<', 'E', 'O', 'F', '>' }; memStream.Write(Encoding.UTF8.GetBytes(eofbuf), 0, eofbuf.Length); //try to send the raw bytes in the memory stream to the remote socket try { //result of 5 hour debug session: dont use memstream.getBuffer() //it returns all bytes in the buffer, not only the ones used..use ToArray() instead!! ClientSslStream.Write(memStream.ToArray()); ClientSslStream.Flush(); } catch (Exception ex) { throw new Exception("ClientSslStream.Write() error: " + ex.Message); } //DEBUG /* StreamWriter sw = new StreamWriter("CommandSent.xml"); sw.Write(new string(Encoding.UTF8.GetChars(memStream.ToArray()))); sw.Close();*/ memStream.Close(); return true; }
///////////////////////////////////////////////////// // // // LoadDynamicGUIDs() // // // ///////////////////////////////////////////////////// //Description: loads all dynamic GUIDs supplied and adds // a static guid to our registry guid signatures // object for each expanded value. // //Returns: true if successful ///////////////////////////////////////////////////// internal bool LoadDynamicGUIDs(ref CwXML.RegistryGuidSignature[] GuidSignatures) { Regex GUIDvalidator = new Regex(@"^(\{){0,1}[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}(\}){0,1}$", RegexOptions.Compiled); //first get a count of how many Dynamic Guids there are int NumDynamicGuids = 0; foreach (CwXML.RegistryGuidSignature sig in GuidSignatures) if (sig.GuidType == "Dynamic") NumDynamicGuids++; //none to process. if (NumDynamicGuids == 0) return true; //allocate a new object to store that many dynamic guids ArrayList DynamicSigsToAdd = new ArrayList(NumDynamicGuids); //loop through all our GUID signatures and extract GUIDs from this live system's registry //that are stored at the registry key indicated by the given Dynamic GUID signature foreach (CwXML.RegistryGuidSignature sig in GuidSignatures) { string keyName = sig.GuidValue; string type = sig.GuidType;//"Static" or "Dynamic" string GUID = keyName; //by default, we assume it's static string valueName = ""; //skip any static Guid Signatures -- our goal here is to MAKE static sigs //from dynamic sigs that must be populated at runtime if (type == "Static") continue; //hive parsing vars int firstSlashInKeyName = keyName.IndexOf('\\'); string hive = keyName.Substring(0, firstSlashInKeyName); string keyWithoutHive = keyName.Substring(firstSlashInKeyName, keyName.Length - firstSlashInKeyName); keyName = keyWithoutHive; RegistryKey key; if (hive == "HKLM") key = Registry.LocalMachine; else if (hive == "HKCR") key = Registry.ClassesRoot; else if (hive == "HKU") key = Registry.Users; else if (hive == "HKCC") key = Registry.CurrentConfig; else { RegistryHelperLog.AppendLine("ERROR: Invalid hive supplied in GUID: '" + keyName + "', skipping.."); continue; } //it is possible this Dynamic Guid signature has an embedded {SID} expansion var. //so try to expand this key. string[] expandedKeys = ExpandRegistryKey(keyName, GuidSignatures); string thisKeyName; //if nothing was expanded, use the original key if (expandedKeys == null) expandedKeys = new string[] { keyName }; //loop through all resulting records that were expanded, foreach (string expandedKey in expandedKeys) { thisKeyName = expandedKey.Trim(new char[] { ' ', '\\' }); //try to open the (by now chopped up) keyName .. RegistryKey loadedKey = key.OpenSubKey(thisKeyName); //bail if cant open key if (loadedKey == null) { RegistryHelperLog.AppendLine("ERROR: Could not load GUID, invalid key specified: '" + thisKeyName + "', skipping..."); continue; } //great! loaded the key.. now try to get the value data stored at the value Name object obj = loadedKey.GetValue(valueName); if (obj == null) { RegistryHelperLog.AppendLine("ERROR: Could not load GUID, invalid value name specified: '" + valueName + "', skipping..."); continue; } //sweet! got the value name data..make sure it's a legit GUID if (!GUIDvalidator.IsMatch(obj.ToString())) { RegistryHelperLog.AppendLine("ERROR: Found a GUID value, but it's invalid: '" + obj.ToString() + "', skipping..."); continue; } //store the value GUID = (string)obj.ToString(); //strip out curly braces from GUID if present GUID = GUID.Replace("{", ""); GUID = GUID.Replace("}", ""); //add it as a static GUID to our tmp signatures CwXML.RegistryGuidSignature g = new CwXML.RegistryGuidSignature(); g.GuidType = "Static"; g.GuidValue = GUID; DynamicSigsToAdd.Add(g); } } //add all dynamic guids that are now static guids into our guid sigs array foreach (CwXML.RegistryGuidSignature g in DynamicSigsToAdd) { if (g.GuidType != null && g.GuidValue != null) { //resize our permanent array by 1 Array.Resize(ref GuidSignatures, GuidSignatures.Length + 1); GuidSignatures[GuidSignatures.Length] = g; } } return true; }
// //Allows the user to select a template file that represents predefined file, registry, //and memory signatures for a given piece of malware // private void loadSignatureTemplateToolStripMenuItem_Click(object sender, EventArgs e) { int numRegImported = 0; int numRegGuidImported = 0; int numFileImported = 0; int numMemoryImported = 0; int numDuplicatesIgnored = 0; int numErrorSkipped = 0; //----------------------------------------------- // DETERMINE IMPORT FILENAME //----------------------------------------------- //show browse dialog to select file OpenFileDialog dlg = new OpenFileDialog(); dlg.CheckFileExists = true; dlg.CheckPathExists = true; dlg.DefaultExt = ".xml"; //default extension dlg.Title = "Select signature template file"; dlg.Filter = "XML Files|*.xml"; dlg.Multiselect = true; //the user clicked cancel if (dlg.ShowDialog() != DialogResult.OK) return; int filecount = dlg.FileNames.Length; CwXML.CodewordSignatureTemplate cwt = new CwXML.CodewordSignatureTemplate(); CwXML xml = new CwXML(); // //loop through list of filenames selected and deserialize each XML //document into an instantation of the CwTemplate class // foreach (string filename in dlg.FileNames) { //deserialize the data stored in this signature file try { cwt = xml.ImportSignatureTemplate(filename); } catch (Exception ex) { MessageBox.Show(ex.Message); return; } //we successfully deserialized the XML document, now load it into //the GUI form at the appropriate locations CwXML.RegistrySignature[] regSigs = cwt.RegistrySignatures; CwXML.RegistryGuidSignature[] regGuidSigs = cwt.RegistryGuidSignatures; CwXML.FileSignature[] fileSigs = cwt.FileSignatures; CwXML.MemorySignature[] memSigs = cwt.MemorySignatures; //REG sigs foreach (CwXML.RegistrySignature rs in regSigs) { ListViewItem lvi = new ListViewItem(new string[] { rs.KeyName, rs.ValueName, rs.ValueData, rs.ChangeValueData, rs.Action }); if (RegistrySignatures_Listview.Items.Contains(lvi)) { numDuplicatesIgnored++; continue; } RegistrySignatures_Listview.Items.Add(lvi); numRegImported++; } //REG GUID sigs foreach (CwXML.RegistryGuidSignature rsg in regGuidSigs) { ListViewItem lvi = new ListViewItem(new string[] { rsg.GuidValue, rsg.GuidType }); if (RegistryGuidSignatures_Listview.Items.Contains(lvi)) { numDuplicatesIgnored++; continue; } RegistryGuidSignatures_Listview.Items.Add(lvi); numRegGuidImported++; } //FILE sigs foreach (CwXML.FileSignature fs in fileSigs) { ListViewItem lvi = new ListViewItem(new string[] { fs.FileName, fs.FileHash, fs.FileHashType, fs.FileSize.ToString(), fs.FilePEHeaderSignature, fs.Action }); if (FileSignatures_Listview.Items.Contains(lvi)) { numDuplicatesIgnored++; continue; } FileSignatures_Listview.Items.Add(lvi); numFileImported++; } //MEM sigs foreach (CwXML.MemorySignature ms in memSigs) { ListViewItem lvi = new ListViewItem(new string[] { ms.ProcessName, ms.Keywords, ms.Action }); if (MemorySignatures_Listview.Items.Contains(lvi)) { numDuplicatesIgnored++; continue; } MemorySignatures_Listview.Items.Add(lvi); numMemoryImported++; } } MessageBox.Show("Successfully imported signatures from " + filecount + " files!\n\nSignatures imported:\nRegistry: " + numRegImported + "\nRegistry GUID: " + numRegGuidImported + "\nFile: " + numFileImported + "\nMemory: " + numMemoryImported + "\n\nDuplicates ignored: " + numDuplicatesIgnored + "\nSkipped due to errors: " + numErrorSkipped); }
///////////////////////////////////////////////////// // // // PrintRegistryFindings() // // // ///////////////////////////////////////////////////// //Description: stores findings in passed-in stringbuilder //Returns: nothing ///////////////////////////////////////////////////// internal void PrintRegistryFindings(CwXML.RegistrySignatureMatch[] matches, ref StringBuilder output) { output.AppendLine(""); output.AppendLine("REPORT: ******************************"); output.AppendLine("REPORT: Registry Findings"); output.AppendLine("REPORT: ******************************"); output.AppendLine(""); output.AppendLine("Key Name\t\tValue Name\t\tValue Data\t\tAction\t\tAction Successful\t\tOn Disk?"); if (matches.Length == 0) { output.AppendLine("REPORT: No registry signatures were found."); } else { //loop through all match records foreach (CwXML.RegistrySignatureMatch match in matches) { output.AppendLine(""); output.Append(match.RegistryKeyName + "\t\t"); output.Append(match.RegistryValueName + "\t\t"); output.Append(match.RegistryValueData + "\t\t"); output.Append(match.Action + "\t\t"); output.Append(match.ActionSuccessful.ToString() + "\t\t"); output.Append(match.IsFileOnDisk.ToString()); } } output.AppendLine(""); output.AppendLine("REPORT: ******************************"); output.AppendLine(""); }
///////////////////////////////////////////////////// // // // PrintFileFindings() // // // ///////////////////////////////////////////////////// //Description: prints file signature findings //Returns: nothing ///////////////////////////////////////////////////// internal void PrintFileFindings(CwXML.FileSignatureMatch[] matches, ref StringBuilder output) { output.AppendLine(""); output.AppendLine("REPORT: ******************************"); output.AppendLine("REPORT: File Findings"); output.AppendLine("REPORT: ******************************"); output.AppendLine(""); output.AppendLine("Full Path\t\tSize\t\tHash (Type)\t\tPE Sig.\t\tCreated\t\tAccessed\t\tModified\t\tAction\t\tAction Successful\t\tOn Disk?"); if (matches.Length == 0) { output.AppendLine("REPORT: No file signatures were found."); } else { //loop through all match records foreach (CwXML.FileSignatureMatch match in matches) { output.AppendLine(""); output.Append(match.FullPath + "\t\t"); output.Append(match.FileSize.ToString() + "\t\t"); output.Append(match.FileHash+" ("+match.FileHashType+")" + "\t\t"); output.Append(match.FilePEHeaderSignature + "\t\t"); output.Append(match.FileCreationDate + "\t\t"); output.Append(match.FileLastAccessDate + "\t\t"); output.Append(match.FileLastModifiedDate + "\t\t"); output.Append(match.Action + "\t\t"); output.Append(match.ActionSuccessful.ToString() + "\t\t"); } } output.AppendLine(""); output.AppendLine("REPORT: ******************************"); output.AppendLine(""); }
///////////////////////////////////////////////////// // // // CleanRegistryFindings() // // // ///////////////////////////////////////////////////// //Description: Applies desired action to all registry // findings and stores the result in // the passed-in structure (success/fail). //Returns: none ///////////////////////////////////////////////////// internal void CleanRegistryFindings(ref CwXML.RegistrySignatureMatch[] matches, bool RemoveFileReferencesFromDisk) { //the line below is added for compatibility when calling this function //over remote channels from the admin console if (RegistryHelperLog == null) RegistryHelperLog = new StringBuilder(); RegistryHelperLog.AppendLine("CLEAN: Cleaning "+matches.Length.ToString()+" registry findings..."); RegistryKey key; int count = 0; //************************************* // DELETE ANY BAD FILES FROM DISK // THAT WERE FOUND IN REGISTRY //************************************* if (RemoveFileReferencesFromDisk) { RegistryHelperLog.AppendLine("CLEAN: Removing any malware files referenced in registry findings..."); //loop through all registry findings and if "ISFileOnDisk" = "true", then delete it foreach (CwXML.RegistrySignatureMatch match in matches) { if (match.IsFileOnDisk) { try { File.Delete(match.RegistryValueData); } catch(Exception ex) { RegistryHelperLog.AppendLine("CLEAN: Failed to delete file '"+match.RegistryValueData+"': "+ex.Message); matches[count].ActionSuccessful = false; count++; continue; } matches[count].ActionSuccessful = true; } count++; } } count = 0; //************************************* // APPLY GIVEN ACTION TO MATCH //************************************* //now loop thru the value names again and delete as necessary foreach (CwXML.RegistrySignatureMatch match in matches) { string keyName = match.RegistryKeyName; string valueName = match.RegistryValueName; string action = match.Action; string valueData = match.RegistryValueData; string changeValueData = match.RegistryChangeValueData; int firstSlashInKeyName = keyName.IndexOf('\\'); string hive = keyName.Substring(0, firstSlashInKeyName).ToUpper(); string keyWithoutHive = keyName.Substring(firstSlashInKeyName+1, keyName.Length - (firstSlashInKeyName+1)); keyName = keyWithoutHive; if (hive == "HKLM") key = Registry.LocalMachine; else if (hive == "HKCR") key = Registry.ClassesRoot; else if (hive == "HKU") key = Registry.Users; else if (hive == "HKCC") key = Registry.CurrentConfig; else { RegistryHelperLog.AppendLine("WARNING: Invalid hive detected in registry indicator:"); RegistryHelperLog.AppendLine(" Key: '" + keyName + "'"); RegistryHelperLog.AppendLine(" Parsed hive: '" + hive + "'"); RegistryHelperLog.AppendLine("WARNING: Skipping this indicator..."); continue; //skip if bad hive } RegistryKey parentKey = key.OpenSubKey(keyName, true); if (parentKey != null) { try { object obj = parentKey.GetValue(valueName); if (obj != null) { //try to delete just this value name if (action == "Delete" || action == "Delete All") { RegistryHelperLog.AppendLine("SCAN: Deleting '" + keyName + "\\" + valueName + "'..."); try { parentKey.DeleteValue(valueName); } catch (Exception e) { matches[count].ActionSuccessful = false; count++; RegistryHelperLog.AppendLine("ERROR: Caught exception '" + e.Message + "', not deleting this value name."); continue; } matches[count].ActionSuccessful = true; } else if (action == "Change...") { //the value data will be what we want to change it to RegistryHelperLog.AppendLine("SCAN: Setting '" + keyName + "\\" + valueName + "' = '" + changeValueData + "'..."); try { parentKey.SetValue(valueName, changeValueData); } catch (Exception e) { matches[count].ActionSuccessful = false; count++; RegistryHelperLog.AppendLine("ERROR: Caught exception '" + e.Message + "', not changing this value name."); continue; } matches[count].ActionSuccessful = true; } else if (action == "Clear") { //the value data will be what we want to change it to RegistryHelperLog.AppendLine("SCAN: Clearing '" + keyName + "\\" + valueName + "'..."); try { parentKey.SetValue(valueName, ""); } catch (Exception e) { matches[count].ActionSuccessful = false; count++; RegistryHelperLog.AppendLine("ERROR: Caught exception '" + e.Message + "', not clearing this value name."); continue; } matches[count].ActionSuccessful = true; } } else { RegistryHelperLog.AppendLine("SCAN: The value name '" + keyName + "\\" + valueName + "' doesn't exist, not modifying."); matches[count].ActionSuccessful = false; } } catch (Exception e) { RegistryHelperLog.AppendLine("SCAN: Caught exception '" + e.Message + "', can't get this value name."); matches[count].ActionSuccessful = false; count++; continue; } } else { matches[count].ActionSuccessful = false; RegistryHelperLog.AppendLine("SCAN: Failed to open parent key '" + keyName + "'..."); } count++; } // ** EXTREMELY IMPORTANT ** // //must FLUSH the registry to force oS to synch in-memory cached registry //to the on-disk registry so that subsequent scans dont pick up results //that were cleaned but not synched yet. //This "out of synch" issue occasionally happens and shouldn't according to MSDN: /* * "It is not necessary to call Flush to write out changes to a key. Registry changes are flushed * to disk when the registry uses its lazy flusher. Lazy flushing occurs automatically and * regularly after a system-specified time interval. Registry changes are also flushed to disk at system shutdown. * Unlike Close, the Flush function returns only when all the data has been written to the registry. * The Flush function might also write out parts of or all of the other keys. Calling this function excessively * can have a negative effect on an application's performance. * An application should only call Flush if it must be absolute certain that registry changes are recorded to disk. * In general, Flush rarely, if ever, need be used." * */ //flush them all, even if we didn't write to them. Registry.LocalMachine.Flush(); Registry.ClassesRoot.Flush(); Registry.Users.Flush(); Registry.CurrentConfig.Flush(); }
///////////////////////////////////////////////////// // // // CopyResponseToLogWindow() // // // ///////////////////////////////////////////////////// //Description: copies the fields of the given response // object to the log window pane. // //Returns: void ///////////////////////////////////////////////////// private void CopyResponseToLogWindow(CwXML.CodewordAgentResponse response) { string currentLogWindowText = LogWindow.Text; string newLogText = ""; LastCommandPane.Text = ""; LastCommandPane.Text += "COMMAND: " + response.CommandCodeReceived.ToString() + "\r\n"; LastCommandPane.Text += "RESPONSE: " + response.ResponseCode + "\r\n"; //ResponseInfo is optional, so check for null if (response.ResponseInfo != null) LastCommandPane.Text += "INFO: " + response.ResponseInfo + "\r\n"; //ResponseLog is optional, so check for null if (response.ResponseLog != null) newLogText += response.ResponseLog.Replace("\n", "\r\n"); //ResponseData is optional if (response.ResponseData != null) newLogText += response.ResponseData.Replace("\n", "\r\n"); //tack on the new info to the top of the log window if (newLogText != "") newLogText += "\r\n\r\n++++++++++++++++++++++++++++++\r\n"; LogWindow.Text = newLogText+currentLogWindowText+"\r\n"; }
///////////////////////////////////////////////////// // // // CleanFileFindings() // // // ///////////////////////////////////////////////////// //Description: deletes any files from disk if indicated. //Returns: nothing; side-effect on passed-in results ///////////////////////////////////////////////////// internal bool CleanFileFindings(ref CwXML.FileSignatureMatch[] FileSignatureMatches) { //the line below is added for compatibility when calling this function //over remote channels from the admin console if (AgentScanLog == null) AgentScanLog = new StringBuilder(); int count = 0; // //clean files as directed for file sig matches // foreach (CwXML.FileSignatureMatch match in FileSignatureMatches) { string action = match.Action; //try to delete the file if (action == "Delete if found") { try { File.Delete(match.FullPath); } catch(Exception ex) { string t = ex.Message; AgentScanLog.AppendLine("CLEAN: Failed to remove file '"+match.FullPath+"'!"); FileSignatureMatches[count].ActionSuccessful = false; count++; continue; } FileSignatureMatches[count].ActionSuccessful = true; } count++; } return true; }
///////////////////////////////////////////////////// // // // ScanForFileSignatures() // // // ///////////////////////////////////////////////////// //Description: Scans all logical drives for the given // file signatures. //Returns: nothing; side-effect on passed-in results ///////////////////////////////////////////////////// internal void ScanForFileSignatures(CwXML.FileSignature[] FileSignatures, ref CwXML.FileSignatureMatch[] matches) { //get list of logical drives string[] drives = Environment.GetLogicalDrives(); int numMalware=0; ArrayList matchList; ArrayList matchRecordsList=new ArrayList(); AgentScanLog.AppendLine("SCAN: Drives: " + string.Join(",", drives)); //----------------------------------------------------------- // SCAN DISKS FOR FILE SIGNATURE MATCHES //----------------------------------------------------------- //loop through all our disk drives - returns it as C:\, D:\, F:\ foreach (string drive in drives) { AgentScanLog.AppendLine("SCAN: Scanning " + drive + "..."); //loop through all signatures foreach (CwXML.FileSignature signature in FileSignatures) { //perform search based on parameters above (some may be empty) try { matchList = FileSearch(drive, signature.FileName, signature.FileHash, signature.FileHashType, signature.FileSize.ToString(), signature.FilePEHeaderSignature); } catch (Exception ex) { AgentScanLog.AppendLine("SCAN: Failed to scan drive: " + ex.Message); break; //dont continue scanning for signatures on this drive. } AgentScanLog.AppendLine("SCAN: There were "+matchList.Count.ToString()+" matches for this signature."); //if we got a match, add those results to our array of arrays if (matchList.Count > 0) { AgentScanLog.AppendLine("file search matches: " + string.Join(",", (string[])matchList.ToArray(typeof(string)))); foreach (string fullPathToMatch in matchList) { //get info about this file FileInfo f; try { f = new FileInfo(fullPathToMatch); } catch (Exception ex) { AgentScanLog.AppendLine("SCAN: Error querying file '" + fullPathToMatch + "': " + ex.Message); continue; } CwXML.FileSignatureMatch fm = new CwXML.FileSignatureMatch(); fm.FileName = f.Name; fm.FileSize = f.Length; fm.FullPath = f.FullName; //if no file hash was specified in the signature, create one now (MD5 only) if (signature.FileHash == "") { fm.FileHash = GetMD5HashOfFile(f.FullName); fm.FileHashType = "MD5"; } else { fm.FileHash = signature.FileHash; fm.FileHashType = signature.FileHashType; } //if PE header signature was given in signature, save it if (signature.FilePEHeaderSignature != "") fm.FilePEHeaderSignature = signature.FilePEHeaderSignature; fm.Action = signature.Action; //get various file attribs fm.FileLastAccessDate = f.LastAccessTime.ToLongDateString(); fm.FileLastModifiedDate = f.LastWriteTime.ToLongDateString(); fm.FileCreationDate = f.CreationTime.ToLongDateString(); //add it to list matchRecordsList.Add(fm); } numMalware++; } } AgentScanLog.AppendLine("SCAN: Scan of " + drive + " complete (" + numMalware.ToString() + " malicious files found)."); numMalware = 0; } //we've scanned all disks for all file signatures. //if we got matches, create a match record for them if (matchRecordsList.Count > 0) { matches = new CwXML.FileSignatureMatch[matchRecordsList.Count]; int i = 0; foreach (CwXML.FileSignatureMatch matchRecord in matchRecordsList) { matches[i] = new CwXML.FileSignatureMatch(); matches[i] = matchRecord; i++; } } else matches = new CwXML.FileSignatureMatch[0]; }