Menu

Creating a Rule in Microsoft Sentinel to Detect a ZuoRAT Malware Infection

Creating a Rule in Microsoft Sentinel to Detect a ZuoRAT Malware Infection

Lumen Technologies Inc.'s and Black Lotus Labs' research staff have recently detected a remote access Trojan called ZuoRAT which, at the moment, is not usually detected by antivirus software. This malware targets the following SOHO router models (short for Small Office Home Office, routers that are designed for home offices or small offices):

  • Cisco RV 320, 325 AND 420.
  • Asus RT-AC68U, RT-AC530, RT-AC68P and RT-AC1900U.
  • DrayTek Vigor 3900.
  • NETGEAR devices.
Although, according to the report made by the research staff, the ZuoRAT malware could not only be limited to attacking these SOHO router models.

The malware would reach the devices through the network, using as an attack vector vulnerabilities in these routers that have not been fixed (such as CVE-2020-26878 and CVE-2020-26879). According to Incibe, a successful infection would allow the cybercriminal to collect information about the network on which the router is located in order to gain persistent access to the local network and, subsequently, to carry out Man-in-the-Middle attacks and DNS and HTTPS hijacking. So it is essential to monitor routers and apply security patches every time a new one becomes available.

In this post we show a query for Microsoft Sentinel that, configured in the 'Analytics rules' section, will alert us if our device has been infected with the ZuoRAT malware. At the end of the post we add some recommendations to reduce the probability of being infected (spoiler: the router needs to be updated).

 

Creation of the Analytic Rule in Microsoft Sentinel for the detection of the ZuoRAT malware

To create the query in Microsoft Sentinel, go to the 'Analytics' section and then select 'Create' à 'Scheduled query rule'.

We then fill in the general information about the rule. Here is an example:

 

 

In the next tab: 'Set rule logic' we configure the operation of the rule. At this point we introduce the query that Sentinel will use to search for matches in the logs and activate the alert.

For the query we have used the indicators of compromise (IoC) located in the Black Lotus Labs repository available at the following link and to add the IoCs we have used a .csv file located in the Intelequia repository. I add the query below:

 

let iocs = externaldata(DateAdded: string, IoC: string, Type: string, TLP: string) with (format="csv", ignoreFirstRecord=True);

let IPList = (iocs | where Type =~ "ip" | project IoC);

let sha256Hashes = (iocs | where Type == "sha256" | project IoC);

let FilePaths = (iocs | where Type =~ "FilePath" | project IoC);

let POST_URI = (iocs | where Type =~ "URI1" | project IoC);

let GET_URI = (iocs | where Type =~ "URI2" | project IoC);

(union isfuzzy=true

    (CommonSecurityLog

    | where isnotempty(SourceIP) or isnotempty(DestinationIP)

    | where SourceIP in (IPList) or DestinationIP in (IPList) or Message has_any (IPList) or FileHash in (sha256Hashes)

    | extend IPMatch = case(SourceIP has_any (IPList), "SourceIP", DestinationIP has_any (IPList), "DestinationIP", "Message")  

    | summarize StartTimeUtc = min(TimeGenerated), EndTimeUtc = max(TimeGenerated)

        by

        SourceIP,

        DestinationIP,

        DeviceProduct,

        DeviceAction,

        Message,

        Protocol,

        SourcePort,

        DestinationPort,

        DeviceAddress,

        DeviceName,

        IPMatch

    | extend

        timestamp = StartTimeUtc,

        IPCustomEntity = case(IPMatch == "SourceIP", SourceIP, IPMatch == "DestinationIP", DestinationIP, "IP in Message Field")  

    ),

    (OfficeActivity

    | extend SourceIPAddress = ClientIP, Account = UserId

    | where SourceIPAddress has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = SourceIPAddress,

        AccountCustomEntity = Account

    ),

    (_Im_Dns

    | where DstIPAddr has_any (IPList) or SrcIpAddr has_any (IPList)

    | extend DestinationIPAddress = ResponseName, Host = SrcIpAddr

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = DestinationIPAddress,

        HostCustomEntity = Host

    ),

    (_Im_NetworkSession

    | where DstIPAddr has_any (IPList) or SrcIpAddr has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = SrcIpAddr,

        HostCustomEntity = Hostname,

        AccountCustomEntity=User

    ),

    (_Im_NetworkSession

    | where DstIPAddr has_any (IPList) or SrcIpAddr has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = DstIpAddr,

        HostCustomEntity = Hostname,

        AccountCustomEntity = User

    ),

    (WireData  

    | where isnotempty(RemoteIP)

    | where RemoteIP has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = RemoteIP,

        HostCustomEntity = Computer

    ),

    (SigninLogs

    | where isnotempty(IPAddress)

    | where IPAddress has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        AccountCustomEntity = UserPrincipalName,

        IPCustomEntity = IPAddress

    ),

    (AADNonInteractiveUserSignInLogs

    | where isnotempty(IPAddress)

    | where IPAddress has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        AccountCustomEntity = UserPrincipalName,

        IPCustomEntity = IPAddress

    ),

    (W3CIISLog  

    | where isnotempty(cIP)

    | where cIP has_any (IPList) or (csMethod == 'GET' and csUriStem has_any (GET_URI)) or (csMethod == 'POST' and csUriStem has_any (POST_URI))

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = cIP,

        HostCustomEntity = Computer,

        AccountCustomEntity = csUserName

    ),

    (AzureActivity  

    | where isnotempty(CallerIpAddress)

    | where CallerIpAddress has_any (IPList)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = CallerIpAddress,

        AccountCustomEntity = Caller

    ),

    (

    DeviceNetworkEvents

    | where isnotempty(RemoteIP)  

    | where RemoteIP has_any (IPList) or InitiatingProcessSHA256 has_any (sha256Hashes) or InitiatingProcessFolderPath has_any (FilePaths)

    | extend

        timestamp = TimeGenerated,

        IPCustomEntity = RemoteIP,

        HostCustomEntity = DeviceName  

    ),

    (

    AzureDiagnostics

    | where ResourceType == "AZUREFIREWALLS"

    | where Category == "AzureFirewallApplicationRule"

    | parse msg_s with Protocol 'request from ' SourceHost ':' SourcePort 'to ' DestinationHost ':' DestinationPort '. Action:' Action

    | where isnotempty(DestinationHost)

    | where DestinationHost has_any (IPList)  

    | extend DestinationIP = DestinationHost

    | extend IPCustomEntity = SourceHost

    ),

    (

    AzureDiagnostics

    | where ResourceType == "AZUREFIREWALLS"

    | where Category == "AzureFirewallNetworkRule"

    | parse msg_s with Protocol 'request from ' SourceHost ':' SourcePort 'to ' DestinationHost ':' DestinationPort '. Action:' Action

    | where isnotempty(DestinationHost)

    | where DestinationHost has_any (IPList)  

    | extend DestinationIP = DestinationHost

    | extend IPCustomEntity = SourceHost

    ),

    (Event

    | where Source == "Microsoft-Windows-Sysmon" and EventID == '7'

    | extend EvData = parse_xml(EventData)

    | extend EventDetail = EvData.DataItem.EventData.Data

    | extend ImageLoaded = EventDetail.[5].["#text"], Hashes = EventDetail.[11].["#text"]

    | parse Hashes with * 'SHA256=' SHA256 '",' *

    | where ImageLoaded has_any (FilePaths) or SHA256 has_any (sha256Hashes)

    | project

        TimeGenerated,

        EventDetail,

        UserName,

        Computer,

        Type,

        Source,

        SHA256,

        ImageLoaded,

        EventID

    | extend

        Type = strcat(Type, ":", EventID, ": ", Source),

        Account = UserName,

        FileHash = SHA256,

        Image = EventDetail.[4].["#text"]

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = tostring(split(Image, '\\', -1)[-1]),

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (DeviceEvents

    | extend FilePath = strcat(FolderPath, '\\', FileName)

    | where InitiatingProcessSHA256 has_any (sha256Hashes) or FilePath has_any (FilePaths)

    | project

        TimeGenerated,

        ActionType,

        DeviceId,

        DeviceName,

        InitiatingProcessAccountDomain,

        InitiatingProcessAccountName,

        InitiatingProcessCommandLine,

        InitiatingProcessFolderPath,

        InitiatingProcessId,

        InitiatingProcessParentFileName,

        InitiatingProcessFileName,

        InitiatingProcessSHA256,

        Type

    | extend

        Account = InitiatingProcessAccountName,

        Computer = DeviceName,

        CommandLine = InitiatingProcessCommandLine,

        FileHash = InitiatingProcessSHA256,

        Image = InitiatingProcessFolderPath

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = InitiatingProcessFileName,

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (DeviceFileEvents

    | where FolderPath has_any (FilePaths) or SHA256 has_any (sha256Hashes)

    | project

        TimeGenerated,

        ActionType,

        DeviceId,

        DeviceName,

        InitiatingProcessAccountDomain,

        InitiatingProcessAccountName,

        InitiatingProcessCommandLine,

        InitiatingProcessFolderPath,

        InitiatingProcessId,

        InitiatingProcessParentFileName,

        InitiatingProcessFileName,

        InitiatingProcessSHA256,

        Type

    | extend

        Account = InitiatingProcessAccountName,

        Computer = DeviceName,

        CommandLine = InitiatingProcessCommandLine,

        FileHash = InitiatingProcessSHA256,

        Image = InitiatingProcessFolderPath

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = InitiatingProcessFileName,

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (DeviceImageLoadEvents

    | where FolderPath has_any (FilePaths) or SHA256 has_any (sha256Hashes)

    | project

        TimeGenerated,

        ActionType,

        DeviceId,

        DeviceName,

        InitiatingProcessAccountDomain,

        InitiatingProcessAccountName,

        InitiatingProcessCommandLine,

        InitiatingProcessFolderPath,

        InitiatingProcessId,

        InitiatingProcessParentFileName,

        InitiatingProcessFileName,

        InitiatingProcessSHA256,

        Type

    | extend

        Account = InitiatingProcessAccountName,

        Computer = DeviceName,

        CommandLine = InitiatingProcessCommandLine,

        FileHash = InitiatingProcessSHA256,

        Image = InitiatingProcessFolderPath

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = InitiatingProcessFileName,

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (Event

    | where Source == "Microsoft-Windows-Sysmon"

    | extend EvData = parse_xml(EventData)

    | extend EventDetail = EvData.DataItem.EventData.Data

    | parse EventDetail with * 'SHA256=' SHA256 '",' *

    | where EventDetail has_any (sha256Hashes)

    | project TimeGenerated, EventDetail, UserName, Computer, Type, Source, SHA256

    | extend

        Type = strcat(Type, ": ", Source),

        Account = UserName,

        FileHash = SHA256,

        Image = EventDetail.[4].["#text"]

    | extend

        timestamp = TimeGenerated,

        HostCustomEntity = Computer,

        AccountCustomEntity = Account,

        ProcessCustomEntity = tostring(split(Image, '\\', -1)[-1]),

        AlgorithmCustomEntity = "SHA256",

        FileHashCustomEntity = FileHash

    ),

    (imFileEvent

    | where TargetFileSHA256 has_any (sha256Hashes) or FilePath has_any (FilePaths)

    | extend

        Account = ActorUsername,

        Computer = DvcHostname,

        IPAddress = SrcIpAddr,

        CommandLine = ActingProcessCommandLine,

        FileHash = TargetFileSHA256

    | project Type, TimeGenerated, Computer, Account, IPAddress, CommandLine, FileHash

    ),

    (VMConnection

    | where Direction == 'outbound' and DestinationIp has_any (IPList)

    )

)

  

 

 

IMPORTANT: This query exceeds 10,000 characters because it is a generic query that addresses multiple tables so that it can be applied to as many environments as possible. When using it, it is necessary to delete the tables that are not going to be consulted. An example: if in our environment we do not have an IIS server from which we have logs to analyze, we delete the part corresponding to the 'W3CIISLog' table.

Then, we introduce the query, delete the tables that we are not going to use and test if it works by clicking on the 'Test with current data' option. If it does not throw any error message, we continue configuring the rule.  

The next step is to configure the entity mapping (to add context in case an alert is triggered and to be able to investigate by Host, Account and IP in case there are more associated alerts):

 

With this part completed, we move on to the planning of the consultation:

 

When we finish this part, we go directly to the 'Review and Create' tab because the rest can be left as it is by default.
 

What recommendations can we carry out?

  • Review the security communications from each affected manufacturer. An example: Asus published a security advisory on June 30, 2022 regarding the ZuoRAT malware: ASUS Product Security Advisory | ASUS Global.
  • Apply the security measures recommended by the SOHO router manufacturer regarding the ZuoRAT malware.
  • Install security updates and patches.
  • Consider contracting managed security services for communications monitoring.
  • Use the AntiBotnet service of the Office of Internet Security (OSI) to detect if our public IP is part of a botnet.


If you have any questions or think we can help your organization, please do not hesitate to contact us. We will be happy to help you😊

 
Categories
Related posts
What role does a SOC service play in business efficiency?
By Aitor Berjano González  |  07 November 2023

SOC services have become an efficient solution for data protection, security and ensuring business operations.

Read more
AI, the New Ally of Cybersecurity
By César Asin Mogica  |  06 November 2023

Learn here the latest advances in artificial intelligence in the field of cybersecurity that we met at the Madrid Tech Show.

Read more
Microsoft Purview: Data governance, compliance and more
By Sergio Darias Pérez  |  31 October 2023

Microsoft Purview, the quintessential solution for Discovering, Classifying, Managing and Protecting Corporate Data. More details in the post ⬇

Read more