let FamiliarDomains =
OfficeActivity
| where TimeGenerated between (ago(14d) .. ago(1d))
| where OfficeWorkload == "MicrosoftTeams"
| where Operation in ("ChatCreated", "TeamsSessionStarted", "MessageSent", "MessageCreatedHasLink", "CallParticipantDetail", "MeetingParticipantDetail", "MeetingDetail")
| extend Domain = tostring(split(UserId, "@", 1).[0])
| where isnotempty(Domain)
| distinct Domain;
let RMMSoftware = externaldata(RMMSoftware: string)[@"https://raw.githubusercontent.com/0x706972686f/RMM-Catalogue/refs/heads/main/rmm.csv"] with (format="csv", ignoreFirstRecord=True);
let ExternalTeamsCommunication =
OfficeActivity
| where TimeGenerated >= ago(1d)
| where OfficeWorkload == "MicrosoftTeams"
| where Operation in ("ChatCreated", "CallParticipantDetail")
| extend UserDomain = tostring(split(UserId, "@", 1).[0])
| where isnotempty(UserDomain)
| where UserDomain !in (FamiliarDomains)
| extend OperationDescription = case(
Operation == "ChatCreated",
"A new chat was created in Teams",
Operation == "CallParticipantDetail",
"Call participant details were logged",
"Unknown Operation"
)
| mv-expand Member = todynamic(Members)
| extend TargetUPN1 = tostring(Member.UPN)
| extend TargetDomain1 = tostring(split(TargetUPN1, "@", 1)[0])
| mv-expand Attendees
| extend TargetUPN2 = tostring(Attendees.UPN)
| extend TargetDomain2 = tostring(split(TargetUPN2, "@", 1)[0])
| extend TargetUPN = coalesce(TargetUPN1, TargetUPN2)
| extend TargetDomain = coalesce(TargetDomain1, TargetDomain2)
| project
TimeGenerated,
Operation,
OperationDescription,
ClientIP,
UserId,
UserDomain,
TargetUPN,
TargetDomain,
ChatThreadId
| extend
TargetUPN = tolower(TargetUPN),
TargetDomain = tolower(TargetDomain),
UserId = tolower(UserId),
UserDomain = tolower(UserDomain)
| where TargetUPN != UserId
| summarize
StartTimeTeams = min(TimeGenerated),
Operations = make_set(Operation),
OperationsDescription = make_set(OperationDescription),
ClientIPs = make_set_if(ClientIP, isnotempty(ClientIP)),
ChatThreadIDs = make_set(ChatThreadId)
by UserId, UserDomain, TargetUPN, TargetDomain
| join kind=leftouter (
IdentityInfo
| where TimeGenerated > ago(14d)
| summarize arg_max(TimeGenerated, *) by AccountUPN
| project
TargetUPN = tolower(AccountUPN),
TargetAccountDisplayName = tolower(AccountDisplayName),
TargetAccountName = tolower(AccountName),
TargetSAMAccountName = tolower(SAMAccountName))
on TargetUPN
| project-away TargetUPN1
| project
StartTimeTeams,
Operations,
OperationsDescription,
ClientIPs,
ChatThreadIDs,
UserId,
UserDomain,
TargetUPN,
TargetDomain,
TargetAccountDisplayName,
TargetAccountName,
TargetSAMAccountName;
let RMMUsage =
union
(DeviceNetworkEvents
| where TimeGenerated > ago(1h)
| where ActionType == "ConnectionSuccess"
| where InitiatingProcessFileName has_any (RMMSoftware) or InitiatingProcessParentFileName has_any (RMMSoftware)
| extend
InitiatingProcessAccountName = tolower(InitiatingProcessAccountName),
InitiatingProcessAccountUpn = tolower(InitiatingProcessAccountUpn),
RMMUsageTime = TimeGenerated
| project
RMMUsageTime,
Type,
ActionType,
DeviceName,
DeviceId,
InitiatingProcessAccountName,
InitiatingProcessAccountUpn,
InitiatingProcessFileName,
InitiatingProcessParentFileName,
RemoteUrl,
RemoteIP),
(DeviceFileEvents
| where TimeGenerated > ago(1h)
| where ActionType == "FileCreated"
| where FileName has_any (RMMSoftware)
| extend
InitiatingProcessAccountName = tolower(InitiatingProcessAccountName),
InitiatingProcessAccountUpn = tolower(InitiatingProcessAccountUpn),
RMMUsageTime = TimeGenerated
| project
RMMUsageTime,
Type,
ActionType,
DeviceName,
DeviceId,
InitiatingProcessAccountName,
InitiatingProcessAccountUpn,
FileName,
FolderPath,
InitiatingProcessFileName,
InitiatingProcessFolderPath,
InitiatingProcessParentFileName),
(DeviceProcessEvents
| where TimeGenerated > ago(1h)
| where ActionType == "ProcessCreated"
| where FileName has_any (RMMSoftware)
or InitiatingProcessFileName has_any (RMMSoftware)
or InitiatingProcessParentFileName has_any (RMMSoftware)
| extend
InitiatingProcessAccountName = tolower(InitiatingProcessAccountName),
InitiatingProcessAccountUpn = tolower(InitiatingProcessAccountUpn),
AccountName = tolower(AccountName),
RMMUsageTime = TimeGenerated
| project
RMMUsageTime,
Type,
ActionType,
DeviceName,
DeviceId,
AccountName,
InitiatingProcessAccountName,
AccountUpn,
InitiatingProcessAccountUpn,
FileName,
FolderPath,
InitiatingProcessFileName,
InitiatingProcessFolderPath,
InitiatingProcessParentFileName,
ProcessCommandLine,
InitiatingProcessCommandLine)
| join kind=leftouter (DeviceInfo
| where TimeGenerated > ago(14d)
| extend LoggedOnUser = tolower(tostring(parse_json(LoggedOnUsers).[0].UserName))
| where isnotempty(LoggedOnUser)
| summarize arg_max(TimeGenerated, *) by DeviceId, LoggedOnUser
| project DeviceId, LoggedOnUser)
on DeviceId
| project-away DeviceId1
| extend AccountJoin = coalesce(InitiatingProcessAccountUpn, AccountUpn, AccountName, InitiatingProcessAccountName, LoggedOnUser)
| extend RMMUsed = case(
isnotempty(FileName) and FileName has_any (RMMSoftware),
FileName,
isnotempty(InitiatingProcessFileName) and InitiatingProcessFileName has_any (RMMSoftware),
InitiatingProcessFileName,
isnotempty(InitiatingProcessParentFileName) and InitiatingProcessParentFileName has_any (RMMSoftware),
InitiatingProcessParentFileName,
"Unknown"
)
| project-reorder
RMMUsageTime,
RMMUsed,
Type,
ActionType,
DeviceName,
DeviceId,
AccountJoin,
AccountName,
AccountUpn,
InitiatingProcessAccountName,
InitiatingProcessAccountUpn,
LoggedOnUser,
FileName,
FolderPath,
InitiatingProcessFileName,
InitiatingProcessFolderPath,
InitiatingProcessParentFileName;
let JoinFunc =
ExternalTeamsCommunication
| extend JoinKeys = pack_array(
TargetUPN,
TargetSAMAccountName,
TargetAccountName,
TargetAccountDisplayName
)
| mv-expand JoinKey = JoinKeys
| extend JoinKey = tostring(JoinKey)
| where isnotempty(JoinKey);
JoinFunc
| join kind=inner (
RMMUsage
)
on $left.JoinKey == $right.AccountJoin
| project-away JoinKey, JoinKeys
| where RMMUsageTime > StartTimeTeams
| where datetime_diff('minute', RMMUsageTime, StartTimeTeams) < 240