In our previous blog post about RMM (Remote Management and Monitoring) tools, we highlighted the prevalence of such tooling in nearly every organization’s environment. In today’s world, where many organizations support remote work, RMM tools are frequently utilized to help provide assistance to end users and to allow IT administrators to perform their tasks from the comfort of their own home or a branch office. As mentioned in the previous blog, most organizations on which we have performed threat hunting engagements use two or more RMM tools, demonstrating that there is not always a clear policy on which software is accepted for internal use, and which software could potentially have been installed for malicious purposes.
Given the wide range of different RMM tools available, performing a threat hunt to identify all different available tools used in the organization brings a couple of challenges. In this blog, we’ll dive a little deeper into how we tackled this challenge and share this knowledge so you can use it to keep your organization safe.
It might seem like a straightforward task to gather a list of executable names and domains used by RMMs. Unfortunately, when putting theory into practice, we noticed that it wasn’t as straightforward as it might seem. One of the biggest issues we encountered was the duplicate findings per device, as multiple artifacts were identified.
First of all, we want to rely as little as possible on only one of the fields where the keyword might be present. To get a more elaborate overview, we want to additionally search for keywords in filenames, folder paths, process command lines, and domains.
Using multiple fields to search for our RMM keywords increases the likelihood of detecting an RMM at the cost of having duplicate entries. Another factor to take into account when building the query is to keep in mind how the query language works and follow best-practices to keep the execution time low.
The way we solve duplicate entries is through grouping. We did this by defining a recognizable name for each keyword that we search for. Using Bomgar as an example, we have four entries defined for this RMM. Beyondtrust, bomgar-rdp, bomgarcloud and license.bomgar.
A device that has Bomgar installed will have anywhere from 1 to 4 of these entries on their filesystem. If they have all four, that would generate four entries within our hunting results. Not being able to reduce this number to one in an environment with tens of thousands of hosts would return unnecessary entries. In essence, we only want to know if any Bomgar artifacts are on a device or not. If any artifacts are found, we want to know which artifacts and group them together to end up with only one log entry per device.
Hunting for RMMs through SIEM logs requires the usage of its query langauge. For this blog post we will focus on the Kusto Query Language (KQL), employed in the Microsoft security suite and widely supported by NVISO’s MDR service.
We start off the query by creating a datatable where we define a common name for the tool which matches a keyword. This ensures that regardless of the matched keyword, the identified tool name remains consistent.
let RMMs = datatable(Tool: string, Keyword: string) [
//entries removed for example readability
"Bomgar", "beyondtrust",
"Bomgar", "bomgar-rdp",
"Bomgar", "bomgarcloud",
"Bomgar", "license.bomgar",
//entries removed for example readability
];
Kusto
Next up, we make a list of all keywords we want to look for. This is achieved by selecting the “Keyword” field from the above RMMs table and storing these in a new variable named Keywords.
let Keywords = RMMs | project Keyword;
Kusto
To optimize the execution time, we’re using the “has_any” operator in the next part of the query. It is also the reason why we have multiple values for Bomgar RMM that contain the word “bomgar”. If we would use the “contains” operator, which is a lot slower, we could reduce the keywords “bomgar-rdp”, “bomgarcloud” and “license.bomgar” to just a single “bomgar” keyword. With the “has” operator (and it’s variants), the search happens on indexed terms (three characters or more), significantly increasing the search speed. A downside of this is that if we would use “bomgar” as a keyword, it would not match on “bomgarcloud” as the characters that form the word “bomgar” are not separated by a non-alphanumeric character. This is just one example of utilizing best practices in our queries to minimize search time. It’s especially useful with complex queries covering large amounts of data.
The following step is to define which table we want to use and in which columns we’re looking for the Keyword values from the datatable. More specifically, in the below example we look in the table DeviceEvents for our defined keywords in the columns FileName, FolderPath, ProcessCommandLine and RemoteUrl.
DeviceEvents
| where TimeGenerated > ago(30d) and (FileName has_any(Keywords) or FolderPath has_any(Keywords) or ProcessCommandLine has_any(Keywords) or RemoteUrl has_any(Keywords))
Kusto
The last part of the query, before we start grouping the findings, combines each event with all possible RMMs (indicator and tool), after which we filter for any event matching the indicator, effectively attributing the event to one or more RMM tools. This is done as an optimization to ensure we only map keywords to tools on events which contain keywords.
| extend _=true
| lookup (RMMs | extend _=true) on _
| where FileName has Keyword or FolderPath has Keyword or ProcessCommandLine has Keyword or RemoteUrl has Keyword
Kusto
The query’s final part is to group all the results by DeviceId, DeviceName, and Tool. For each group, a set of evidences is selected. By using make_set_if, we ensure that there are no duplicates.
| summarize
FileNamesEvidences=make_set_if(FileName, FileName has Keyword),
FolderPathEvidences=make_set_if(FolderPath, FolderPath has Keyword),
ProcessCommandLinesEvidences=make_set_if(ProcessCommandLine, ProcessCommandLine has Keyword),
RemoteUrlsEvidences=make_set_if(RemoteUrl, RemoteUrl has Keyword)
by DeviceId, DeviceName, Tool
Kusto
Although it is not included in this version of the query, renamed executables can be detected by searching for keywords in the file’s metadata field “OriginalFileName” or digital signatures.
If you want to perform the showcased hunt, we have included the complete KQL query at the bottom of the page. You can run it on your own and start looking into your own environment to identify any RMM tools that are not allowed or expected.
let RMMs = datatable(Tool: string, Keyword: string) [
"Action One", "action1",
"Addigy", "addigy",
"Aeroadmin", "aeroadmin",
"Alpemix", "alpemix",
"Ammyy", "ammyy",
"AnyDesk", "anydesk",
"AnyPlace", "anyplace",
"AnyViewer", "anyviewer",
"Aomei", "aomei",
"Apemix", "apemix",
"Atera", "atera",
"Auvik", "auvik",
"Auvik", "auvikservice",
"Aweray", "aweray",
"Awesun", "awesun",
"Barracuda", "barracudamsp",
"BeanyWhere", "beanywhere.com",
"Bomgar", "beyondtrust",
"Bomgar", "bomgar-rdp",
"Bomgar", "bomgarcloud",
"Bomgar", "license.bomgar",
"CentraStage", "centrastage",
"Chrome Remote Desktop", "chrome-remote-desktop",
"Chrome Remote Desktop", "chromeremotedesktop",
"Chrome Remote Desktop", "remotedesktop-pa.googleapis",
"Chrome Remote Desktop", "remotedesktop.google.com",
"Comodo", "comodo",
"Comodo", "comodoremotecontrol",
"ConnectWise", "connectwise",
"ConnectWise", "myconnectwise",
"DameWare", "dameware",
"DameWare", "damewareagent",
"DameWare", "swi-rc.com",
"DameWare", "swi-tc.com",
"Datto", "datto",
"DesktopNow", "desktopnow",
"Distant Desktop", "distantdesktop",
"Distant Desktop", "signalserver.xyz",
"DW Service", "dwservice",
"EnterSRL", "entersrl.it",
"FamaTech", "activate.famatech.com",
"Fleet Deck", "fleetdeck",
"Foris", "foris.cloudberrylab.com",
"Get Go", "getgo.com",
"Get Screen", "getscreen",
"GoTo", "goto-rtc.com",
"GoTo", "goto.com",
"GoTo", "gotoassist.com",
"GoTo", "gotomypc",
"GoTo", "gotoopener",
"Hidden Desktop", "hiddendesktop",
"Hidden VNC", "hvnc",
"Iperius", "iperius",
"IslOnline", "islonline.net",
"IT Support 247", "itsupport247.net",
"Kaseya", "kaseya",
"Level", "level-linux",
"Level", "level-windows",
"Level", "level.exe",
"Level", "level.io",
"LiteManager", "litemanager",
"LogicNow", "logicnow",
"LogMeIn", "logme.in",
"LogMeIn", "logmein",
"LogMeIn", "logmeinrescue",
"MeshAgent", "meshagent",
"MeshAgent", "meshcentral",
"MeshAgent", "meshcommander",
"MeshAgent", "meshinstall",
"mRemoteNG", "mremoteng",
"MSP360", "msp360",
"MSP360", "mspbackups",
"N-Able", "n-able",
"N-Able", "rmm-host.com",
"N-Able", "systemmonitor",
"Nano Systems", "nanosystems.it",
"NchUser", "nchuser.com",
"NetSupport", "netsupport",
"NetSupport", "netsupportsoftware.com",
"Ninja", "ninjarmm",
"Nsight", "advanced monitoring agent",
"Optitune", "opti-tune.com",
"Optitune", "optitune",
"Panorama", "panorama9",
"Parsec", "parsec",
"PC Visit", "pcvisit",
"PDQ", "pdq",
"Playanex", "playanex",
"PulseWay", "pulseway",
"Quasar", "quasar",
"Quick Assist", "quick assist",
"Quick Assist", "quickassist",
"Quick Assist", "remoteassistance.support.services.microsoft.com",
"Real VNC", "realvnc",
"Remote Admin", "radmin",
"Remote Admin", "radminte",
"Remote Management", "remote.management",
"Remote PC", "remotepc",
"Remote PC", "remotepcdesktop",
"Remote PC", "rpcdownloader",
"Remote Utilities", "remote utilities",
"Remote Utilities", "remoteadmin.remoteutilities",
"Remotely", "remotelyanywhere.com",
"RustDesk", "rustdesk",
"ScreenConnect", "screenconnect",
"ScreenConnect", "screenmeet",
"Server-Eye", "server-eye",
"ShowMyPC", "showmypc",
"Simple-Help", "rmshelp.me",
"Simple-Help", "simple-help",
"Simple-Help", "simplehelp",
"Simple-Help", "simplehelper",
"SolarWinds", "licenseserver.solarwinds.com",
"SolarWinds", "solarwindsmsp.com",
"SplashTop", "splashtop",
"Supremo", "supremo",
"Supremo", "supremoremotedesktop",
"SynchroMSP", "servably.com",
"SynchroMSP", "synchromsp",
"SynchroMSP", "syncroapi.com",
"SynchroMSP", "syncromsp.com",
"TacticalAgent", "tacticalagent",
"TacticalAgent", "tacticalrmm",
"TeamViewer", "teamviewer",
"TightVNC", "tightvnc",
"TightVNC", "tvnserver",
"TightVNC", "tvnviewer",
"UltraVNC", "ultraviewer",
"UltraVNC", "ultravnc",
"UltraVNC", "vnc-viewer",
"UltraVNC", "vncmanager",
"UltraVNC", "vncviewer",
"VNC", "services.vnc.com",
"XMReality", "vnc-viewer",
"XMReality", "xmreality",
"Zoho", "zoho",
"Zoho", "zohoassist",
"Zoho", "zohomeeting",
];
let Keywords = RMMs | project Keyword;
// Find all events related to any tool
DeviceEvents
| where TimeGenerated > ago(30d) and (FileName has_any(Keywords) or FolderPath has_any(Keywords) or ProcessCommandLine has_any(Keywords) or RemoteUrl has_any(Keywords))
// Attribute events per tool
| extend _=true
| lookup (RMMs | extend _=true) on _
| where FileName has Keyword or FolderPath has Keyword or ProcessCommandLine has Keyword or RemoteUrl has Keyword
// Group events per tool
| summarize
FileNamesEvidences=make_set_if(FileName, FileName has Keyword),
FolderPathEvidences=make_set_if(FolderPath, FolderPath has Keyword),
ProcessCommandLinesEvidences=make_set_if(ProcessCommandLine, ProcessCommandLine has Keyword),
RemoteUrlsEvidences=make_set_if(RemoteUrl, RemoteUrl has Keyword)
by DeviceId, DeviceName, Tool
Kusto