Detecting Koske malware with Wazuh

| by | Wazuh 4.13.0
Post icon

Koske malware is a new, trending malware that Aqua Nautilus first identified in July 2025. It is believed to be an AI-generated malware designed for cryptocurrency mining operations on Linux endpoints. The structure and characteristics of its code suggest that it may have been developed using large language models (LLMs) or automation frameworks. Koske is designed to install cryptocurrency miners that utilize both CPU and GPU resources of infected Linux endpoints, leveraging them to mine more than 18 cryptocurrencies. 

Defenders observed that the Koske malware was delivered through an improperly configured JupyterLab web-based development environment (potentially via CVE-2025-30370). The malware leveraged seemingly harmless polyglot JPEGs of panda bears as lures, with malicious shellcode appended to the image data, making them extremely challenging to detect. Once executed in memory, it deploys rootkits to hide itself and establishes persistence via bash modifications and other systemd services. It adapts dynamically to maintain command and control (C2) communication.

In this blog post, we examine the payloads delivered within Koske malware polyglot JPEGs and utilize the detective capabilities of Wazuh, an open source unified XDR and SIEM, to identify the behavior of the malware. 

Koske malware attack flow

Koske malware behavior 

Below are some of the behaviors observed after the Koske malware is successfully extracted and executed on a Linux endpoint:

  • Koske malware arrives via polyglot JPEG files (innocent-looking panda images with hidden shellcode and C code appended). It extracts the malicious script from the image, then executes it in-memory or compiles rootkits.
  • It fetches cryptominer components using curl, saving them as files with a .koske extension (such as nanominer.koske, SRBMiner-Multi.koske, and cpuMinerTermux.koske). These are typically archives or executables for various miners.
  • It modifies the bash configuration file .bashrc by creating backups or variants like .bashrc.koske. It then tries to establish persistence by further modifying the bash configuration files to ensure the malware reloads on login or boot.
  • It creates hidden files like /dev/shm/.hiddenpid to store miner PIDs, helping the rootkit (often via LD_PRELOAD) conceal processes and evade detection.
  • It launches optimized cryptominers like cpuminer-sse2 or nanominer with specific algorithms (such as yespowertide for Tidecoin, VRSC for VerusCoin). It checks for running instances using pgrep and supports multiple coins with failover.
  • It establishes TCP connections to mining pools to offload hijacked resources for cryptojacking.
  • It deletes some traces of its execution, like bash history and wget history.

Analyzed IOC

Hash typeValue
SHA256f8c6c873e8289ebbd52d3fc5b6552129d7e20f8a3466aca2d6f1dd2cdd578780
cf9000db6b026ce350982a7dac67152d1649b0021d4b3f0b2cc7315b254e7161
ac6d9c301cd098804841e82704257aeb7c9ebb7b3cb2966a1f443ef383238704
60f4b67f349a13fbf6ef1f45eb6c3147e88015571b2de179e32605e2ead651a0

Infrastructure

We use the following infrastructure to demonstrate the detection of Koske malware  with Wazuh:

  • A pre-built, ready-to-use Wazuh OVA 4.13.0, which includes the Wazuh central components (Wazuh server, Wazuh indexer, and Wazuh dashboard). Follow this guide to download and set up the Wazuh virtual machine.
  • An Ubuntu 24 endpoint with the Wazuh agent 4.13.0 installed and enrolled to the Wazuh server. This endpoint is monitored for the Koske malware activities.

Detection rules 

We use SysmonForLinux to enrich several system events’ visibility and create custom detection rules on the Wazuh server to detect the malicious behavior of the Koske malware. 

Ubuntu endpoint

We install SysmonForLinux to enrich the event logs generated on the Ubuntu endpoint. This approach provides detailed information about events such as process creations, network connections, and changes to the file system.

Note

Execute all the commands below as the root user. If you are using a non-root (normal) user, prepend sudo to each command.

  1. Install SysmonForLinux.
  2. Download the SysmonForLinux configuration file:
# wget https://wazuh.com/resources/blog/detecting-sysjoker-backdoor-malware-with-wazuh/sysmonforlinux-config.xml
  1. Use the SysmonForLinux configuration file:
# sysmon -accepteula -i
# sysmon -i sysmonforlinux-config.xml
  1. Enable Sysmon to run at startup: 
# systemctl enable sysmon
# systemctl start sysmon
  1. Add the following configuration within the <ossec_config> block of the /var/ossec/etc/ossec.conf file to forward Sysmon events to the Wazuh server for analysis:
  <localfile>
    <log_format>syslog</log_format>
    <location>/var/log/syslog</location>
  </localfile>
  1. Restart the Wazuh agent to apply the changes:
# systemctl restart wazuh-agent

Wazuh dashboard

Perform the following steps on the Wazuh dashboard to configure the Wazuh server for Koske malware detection.

Creating custom decoders and rules

Wazuh utilizes decoders to extract information from received logs. Follow these steps to download the decoders that can extract information from SysmonForLinux logs. Perform the steps below to create and configure custom decoders and rules for processing and monitoring logs generated from SysmonForLinux.

Decoders

1. Navigate to Server management > Decoders.

2. Click + Add new decoders file.

Creating custom decoders and rules

3. Copy and paste the decoders below and name the file sysmonforlinux_decoder.xml. Click Save.

<decoder name="sysmon-linux">
  <program_name>sysmon</program_name>
</decoder>

<!-- system -->
<!-- EventID -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pEventID\p(\d+)\p/EventID\p</regex>
  <order>system.eventId</order>
</decoder>

<!-- keywords -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pKeywords\p(\.+)\p/Keywords\p</regex>
  <order>system.keywords</order>
</decoder>

<!-- level -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pLevel\p(\d+)\p/Level\p</regex>
  <order>system.level</order>
</decoder>

<!-- channel -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pChannel\p(\.+)\p/Channel\p</regex>
  <order>system.channel</order>
</decoder>

<!-- opcode -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pOpcode\p(\d+)\p/Opcode\p</regex>
  <order>system.opcode</order>
</decoder>

<!-- version -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pVersion\p(\d+)\p/Version\p</regex>
  <order>system.version</order>
</decoder>

<!-- systemTime -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pTimeCreated SystemTime="(\d+-\d+-\d+T\d+:\d+:\d+.\d+\w)"</regex>
  <order>system.systemTime</order>
</decoder>

<!-- eventRecordID -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pEventRecordID\p(\d+)\p/EventRecordID\p</regex>
  <order>system.eventRecordID</order>
</decoder>

<!-- threadID -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">"\sThreadID="(\d+)"/\p</regex>
  <order>system.threadID</order>
</decoder>

<!-- computer -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pComputer\p(\.+)\p/Computer\p</regex>
  <order>system.computer</order>
</decoder>

<!-- task -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pTask\p(\d+)\p/Task\p</regex>
  <order>system.task</order>
</decoder>

<!-- processID -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pExecution\sProcessID="(\d+)"</regex>
  <order>system.processID</order>
</decoder>

<!-- eventdata -->
<!-- originalFileName -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="OriginalFileName"\p(\.+)\p/Data\p</regex>
  <order>eventdata.originalFileName</order>
</decoder>

<!-- image -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Image"\p(\.+)\p/Data\p</regex>
  <order>eventdata.image</order>
</decoder>

<!-- product -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Product"\p(\.+)\p/Data\p</regex>
  <order>eventdata.product</order>
</decoder>

<!-- parentProcessGuid -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="ParentProcessGuid"\p(\.+)\p/Data\p</regex>
  <order>eventdata.parentProcessGuid</order>
</decoder>

<!-- description -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Description"\p(\.+)\p/Data\p</regex>
  <order>eventdata.description</order>
</decoder>

<!-- logonGuid -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="LogonGuid"\p(\.+)\p/Data\p</regex>
  <order>eventdata.logonGuid</order>
</decoder>

<!-- parentCommandLine -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="ParentCommandLine"\p(\.+)\p/Data\p</regex>
  <order>eventdata.parentCommandLine</order>
</decoder>

<!-- processGuid -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="ProcessGuid"\p(\.+)\p/Data\p</regex>
  <order>eventdata.processGuid</order>
</decoder>

<!-- logonId -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="LogonId"\p(\d+)\p/Data\p</regex>
  <order>eventdata.logonId</order>
</decoder>

<!-- parentProcessId -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="ParentProcessId"\p(\d+)\p/Data\p</regex>
  <order>eventdata.parentProcessId</order>
</decoder>

<!-- processId -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="ProcessId"\p(\d+)\p/Data\p</regex>
  <order>eventdata.processId</order>
</decoder>

<!-- currentDirectory -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="CurrentDirectory"\p(\.+)\p/Data\p</regex>
  <order>eventdata.currentDirectory</order>
</decoder>

<!-- utcTime -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="UtcTime"\p(\d+-\d+-\d+T\d+:\d+:\d+.\d+\w)\p/Data\p</regex>
  <order>eventdata.utcTime</order>
</decoder>

<!-- hashes -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Hashes"\p(\.+)\p/Data\p</regex>
  <order>eventdata.hashes</order>
</decoder>

<!-- parentImage -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="ParentImage"\p(\.+)\p/Data\p</regex>
  <order>eventdata.parentImage</order>
</decoder>

<!-- ruleName -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="RuleName"\p(\.+)\p/Data\p</regex>
  <order>eventdata.ruleName</order>
</decoder>

<!-- company -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Company"\p(\.+)\p/Data\p</regex>
  <order>eventdata.company</order>
</decoder>

<!-- commandLine -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="CommandLine"\p(\.+)\p/Data\p</regex>
  <order>eventdata.commandLine</order>
</decoder>

<!-- integrityLevel -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="IntegrityLevel"\p(\.+)\p/Data\p</regex>
  <order>eventdata.integrityLevel</order>
</decoder>

<!-- fileVersion -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="FileVersion"\p(\.+)\p/Data\p</regex>
  <order>eventdata.fileVersion</order>
</decoder>

<!-- user -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="User"\p(\.+)\p/Data\p</regex>
  <order>eventdata.user</order>
</decoder>

<!-- terminalSessionId -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="TerminalSessionId"\p(\.+)\p/Data\p</regex>
  <order>eventdata.terminalSessionId</order>
</decoder>

<!-- parentUser -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="ParentUser"\p(\.+)\p/Data\p</regex>
  <order>eventdata.parentUser</order>
</decoder>


<!-- event 3 specials -->
<!-- protocol -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Protocol"\p(\.+)\p/Data\p</regex>
  <order>eventdata.protocol</order>
</decoder>

<!-- initiated -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Initiated"\p(\.+)\p/Data\p</regex>
  <order>eventdata.initiated</order>
</decoder>

<!-- sourceIsIpv6 -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="SourceIsIpv6"\p(\.+)\p/Data\p</regex>
  <order>eventdata.sourceIsIpv6</order>
</decoder>

<!-- sourceIp -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="SourceIp"\p(\.+)\p/Data\p</regex>
  <order>eventdata.sourceIp</order>
</decoder>

<!-- sourceHostname -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="SourceHostname"\p(\.+)\p/Data\p</regex>
  <order>eventdata.sourceHostname</order>
</decoder>

<!-- sourcePort -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="SourcePort"\p(\.+)\p/Data\p</regex>
  <order>eventdata.sourcePort</order>
</decoder>

<!-- sourcePortName -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="SourcePortName"\p(\.+)\p/Data\p</regex>
  <order>eventdata.sourcePortName</order>
</decoder>

<!-- destinationIsIpv6 -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="DestinationIsIpv6"\p(\.+)\p/Data\p</regex>
  <order>eventdata.destinationIsIpv6</order>
</decoder>

<!-- destinationIp -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="DestinationIp"\p(\.+)\p/Data\p</regex>
  <order>eventdata.DestinationIp</order>
</decoder>

<!-- DestinationHostname -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="DestinationHostname"\p(\.+)\p/Data\p</regex>
  <order>eventdata.destinationHostname</order>
</decoder>

<!-- destinationPort -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="DestinationPort"\p(\.+)\p/Data\p</regex>
  <order>eventdata.destinationPort</order>
</decoder>

<!-- destinationPortName -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="DestinationPortName"\p(\.+)\p/Data\p</regex>
  <order>eventdata.destinationPortName</order>
</decoder>

<!-- event 4 specials -->
<!-- state -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="State"\p(\.+)\p/Data\p</regex>
  <order>eventdata.state</order>
</decoder>

<!-- version -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Version"\p(\.+)\p/Data\p</regex>
  <order>eventdata.version</order>
</decoder>

<!-- schemaVersion -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="SchemaVersion"\p(\.+)\p/Data\p</regex>
  <order>eventdata.schemaVersion</order>
</decoder>

<!-- event 9 specials -->
<!-- device -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Device"\p(\.+)\p/Data\p</regex>
  <order>eventdata.device</order>
</decoder>

<!-- event 11 specials -->
<!-- targetFilename -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="TargetFilename"\p(\.+)\p/Data\p</regex>
  <order>eventdata.targetFilename</order>
</decoder>

<!-- creationUtcTime -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="CreationUtcTime"\p(\d+-\d+-\d+T\d+:\d+:\d+.\d+\w)\p/Data\p</regex>
  <order>eventdata.creationUtcTime</order>
</decoder>

<!-- event 16 specials -->
<!-- configuration -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Configuration"\p(\.+)\p/Data\p</regex>
  <order>eventdata.configuration</order>
</decoder>

<!-- configurationFileHash -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="ConfigurationFileHash"\p(\.+)\p/Data\p</regex>
  <order>eventdata.configurationFileHash</order>
</decoder>


<!-- event 23 specials -->
<!-- isExecutable -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="IsExecutable"\p(\.+)\p/Data\p</regex>
  <order>eventdata.isExecutable</order>
</decoder>

<!-- archived -->
<decoder name="sysmon-linux-child">
  <parent>sysmon-linux</parent>
  <regex offset="after_parent">\pData Name="Archived"\p(\.+)\p/Data\p</regex>
  <order>eventdata.archived</order>
</decoder>
Sysmon for linux decoder
Rules

Wazuh uses rules to analyze decoded log data and trigger alerts. Follow these steps to set up the rules that can trigger alerts from the SysmonforLinux logs.

1. Navigate to Server management > Rules.

2. Click + Add new rules file.

3. Copy and paste the rules below and name the file sysmonforlinux_rules.xml. Click Save.

<group name="linux,sysmon,">
  <rule id="200150" level="1">
    <decoded_as>sysmon-linux</decoded_as>
    <field name="system.eventId">\.+</field>
    <options>no_full_log</options>
    <description>Sysmon For Linux Event</description>
    <mitre>
      <id>T1204</id>
    </mitre>
    <group>sysmon_event</group>
  </rule>

<!-- EventID = 1 -->
  <rule id="200151" level="1">
    <if_sid>200150</if_sid>
    <field name="system.eventId">^1$</field>
    <options>no_full_log</options>
    <description>Sysmon - Event 1: Process creation $(eventdata.image)</description>
    <mitre>
      <id>T1204</id>
    </mitre>
    <group>sysmon_event1</group>
  </rule>

<!-- EventID = 3 -->
  <rule id="200152" level="1">
    <if_sid>200150</if_sid>
    <field name="system.eventId">^3$</field>
    <options>no_full_log</options>
 <description>Sysmon - Event 3: Network connection by $(eventdata.image)</description>
    <group>sysmon_event3</group>
  </rule>

<!-- EventID = 5 -->
  <rule id="200153" level="1">
    <if_sid>200150</if_sid>
    <field name="system.eventId">^5$</field>
    <options>no_full_log</options>
    <description>Sysmon - Event 5: Process terminated $(eventdata.image)</description>
    <mitre>
      <id>T1204</id>
    </mitre>
    <group>sysmon_event5</group>
  </rule>

<!-- EventID = 9 -->
  <rule id="200154" level="1">
    <if_sid>200150</if_sid>
    <field name="system.eventId">^9$</field>
    <options>no_full_log</options>
    <description>Sysmon - Event 9: Raw Access Read by $(eventdata.image)</description>
    <mitre>
      <id>T1204</id>
    </mitre>
    <group>sysmon_event9</group>
  </rule>

<!-- EventID = 11 -->
  <rule id="200155" level="1">
    <if_sid>200150</if_sid>
    <field name="system.eventId">^11$</field>
    <options>no_full_log</options>
    <description>Sysmon - Event 11: FileCreate by $(eventdata.image)</description>
    <group>sysmon_event_11</group>
   </rule>

<!-- EventID = 16 -->
  <rule id="200156" level="1">
    <if_sid>200150</if_sid>
    <field name="system.eventId">^16$</field>
    <options>no_full_log</options>
    <description>Sysmon - Event 16: Sysmon config state changed $(Event.EventData.Data.Configuration)</description>
    <mitre>
      <id>T1562</id>
    </mitre>
    <group>sysmon_event_16</group>
  </rule>

<!-- EventID = 23 -->
  <rule id="200157" level="1">
    <if_sid>200150</if_sid>
    <field name="system.eventId">^23$</field>
    <options>no_full_log</options>
   <description>Sysmon - Event 23: FileDelete (A file delete was detected) by $(eventdata.image)</description>
    <mitre>
      <id>T1485</id>
    </mitre>
    <group>sysmon_event_23</group>
  </rule>

<!-- Overrides -->
<!-- EventID = 3. No alerts for events if Image = /var/ossec/bin/wazuh-agentd -->
  <rule id="200200" level="1">
    <if_sid>200152</if_sid>
    <field name="eventdata.image">wazuh-agentd$</field>
    <options>no_full_log</options>
    <description>Sysmon - Event 3: Network connection by $(eventdata.image)</description>
    <group>sysmon_event3</group>
  </rule>

<!-- EventID = 11. No alerts for events if Image = /var/ossec/bin/wazuh-agentd -->
  <rule id="200201" level="1">
    <if_sid>200155</if_sid>
    <field name="eventdata.image">wazuh-agentd$</field>
    <options>no_full_log</options>
    <description>Sysmon - Event 11: FileCreate by $(eventdata.image)</description>
    <group>sysmon_event_11</group>
  </rule>

<!-- EventID = 23. No alerts for events if Image = /var/ossec/bin/wazuh-agentd -->
  <rule id="200202" level="1">
    <if_sid>200157</if_sid>
    <field name="eventdata.image">wazuh-agentd$</field>
    <options>no_full_log</options>
   <description>Sysmon - Event 23: FileDelete (A file delete was detected) by $(eventdata.image)</description>
    <mitre>
      <id>T1485</id>
    </mitre>
    <group>sysmon_event_23</group>
  </rule>
</group>

4. Copy and paste the rules below to the rule file sysmonforlinux_rules.xml to detect the Koske malware behavior. Click Save.

<group name="koske, malware,">

 <!-- Extract - Detect extraction of shellcode/C code from JPEGs, in-memory compilation/execution -->


  <rule id="100801" level="7" frequency="2" ignore="120">
    <if_sid>200151</if_sid>
    <field name="eventdata.Image" type="pcre2">^/usr/bin/(bash|sh|dd|tail|head|gcc|ld)$</field>
    <field name="eventdata.CommandLine" type="pcre2">skip=[0-9]+ bs=1 if=.*\.jpeg of=.*\.(sh|c)$</field>   
    <description>Possible Koske malware activity: Extraction of shellcode from JPEG $(eventdata.CommandLine) into a shell script for execution.</description>
    <mitre>
      <id>T1059</id>  <!-- Command and Scripting Interpreter -->
      <id>T1027</id>  <!-- Obfuscated Files or Information -->
    </mitre>
  </rule>


 <!-- Download - Curl creating .koske miner files -->
  <rule id="100802" level="7">
    <if_sid>200155</if_sid>
    <field name="eventdata.Image" type="pcre2">^/usr/bin/curl$</field>
    <field name="eventdata.TargetFilename" type="pcre2">\.(koske)$</field> 
    <field name="eventdata.TargetFilename" type="pcre2">nanominer|SRBMiner-Multi|cpuMinerTermux|cpuMinerRplant</field> <!-- Specific miner names -->
    <description>Koske malware activity: Download of miner payload via curl $(eventdata.TargetFilename).</description>
    <mitre>
      <id>T1105</id> <!-- Ingress Tool Transfer -->
    </mitre>
  </rule>


  <!-- Rule for Persistence/Evasion - .bashrc modifications-->
    <rule id="100803" level="9">
    <if_sid>200155, 200151</if_sid> 
    <field name="eventdata.Image" type="pcre2">^/usr/bin/(bash|chmod|grep)$</field>
    <field name="eventdata.TargetFilename" type="pcre2">\.bashrc\.koske$</field>
    <description>Koske malware persistence: creation of modified bashrc file $(eventdata.TargetFilename) to maintain access.</description>
    <mitre>
      <id>T1546.004</id>
    </mitre>
  </rule>


  <rule id="100804" level="9">
    <if_sid>200155</if_sid>
    <field name="eventdata.Image" type="pcre2">^/usr/bin/bash$</field>
    <field name="eventdata.TargetFilename" type="pcre2">^/dev/shm/\.hiddenpid$</field>  
    <description>Koske malware activity: Creation of rootkit persistence file $(eventdata.TargetFilename) for process hiding.</description>
    <mitre>
      <id>T1562</id>  
      <id>T1014</id> 
    </mitre>
  </rule>

  <rule id="100805" level="12" frequency="2" ignore="120">
    <if_sid>200151, 200152</if_sid> 
    <field name="eventdata.Image" type="pcre2">cpuminer-sse2|nanominer|/usr/bin/pgrep</field>
    <field name="eventdata.CommandLine" type="pcre2">yespowertide|stratum-eu\.rplant\.xyz:7059|VRSC|eu\.luckpool\.net:3956|pgrep \./nanominer</field> <!-- Miner cmds/pools -->
    <description>Koske malware activity: Execution of cryptominer $(eventdata.Image) with mining command $(eventdata.CommandLine).</description>
    <mitre>
      <id>T1496</id> <!-- Resource Hijacking -->
    </mitre>
  </rule>

  <rule id="100806" level="9">
    <if_sid>200152</if_sid> 
    <field name="eventdata.Image" type="pcre2">nanominer</field>
    <field name="eventdata.Protocol" type="pcre2">tcp</field>
    <field name="eventdata.DestinationIp" type="pcre2">79\.137\.70\.48</field> <!-- Luckpool IP from log -->
    <description>Koske malware activity: Cryptominer $(eventdata.Image) established TCP connection to mining pool $(eventdata.DestinationIp):$(eventdata.DestinationPort)</description>
    <mitre>
      <id>T1496</id>
    </mitre>
  </rule>


</group>

Where:

  • Rule ID 100801 is triggered when the Koske malware extracts shellcode from a JPEG image and stores it in a shell script for execution.
  • Rule ID 100802 is triggered when the Koske malware downloads miner payloads such as nanominer, SRBMiner, or cpuMinerTermux via curl.
  • Rule ID 100803 is triggered when the Koske malware creates a malicious .bashrc variant to establish persistence.
  • Rule ID 100804 is triggered when the Koske malware creates a rootkit persistence file in /dev/shm to hide processes.
  • Rule ID 100805 is triggered when the Koske malware executes cryptominers (such as cpuminer, nanominer) or performs miner process checks.
  • Rule ID 100806 is triggered when the Koske malware cryptominers establish a TCP connection to the mining pool server. 
sysmonforlinux-rule

5. Click Reload to apply the changes.

Detection results

Follow the steps below to view the alerts generated on the Wazuh dashboard when the Koske malware is executed on the Linux endpoint.

  1. Navigate to Threat intelligence > Threat Hunting and click the Events tab.
  2. Click + Add filter. Then filter by rule.groups.
  3. In the Operator field, select is.
  4. Search and select the rule group Koske in the Values field.
  5. Click Save.
Detection results

Detecting Koske malware using the Wazuh Rootcheck module 

To detect the Koske malware rootkit activities on the monitored Ubuntu endpoint, use the Wazuh Rootcheck module. By default, the Wazuh agent is configured to perform rootcheck scans every 12 hours to identify rootkit-related anomalies, such as hidden files and unauthorized modifications to system files. 

Configuration

  1. If you want rootcheck scans enabled for a shorter frequency, edit the Wazuh agent configuration file  /var/ossec/etc/ossec.conf to reduce scan frequency to your desired duration. This step is optional.
<ossec_config>

<!-- Policy monitoring -->
  <rootcheck>
    <disabled>no</disabled>
    <check_files>yes</check_files>
    <check_trojans>yes</check_trojans>
    <check_dev>yes</check_dev>
    <check_sys>yes</check_sys>
    <check_pids>yes</check_pids>
    <check_ports>yes</check_ports>
    <check_if>yes</check_if>

    <!-- Frequency that rootcheck is executed - every 12 hours -->
    <frequency>1800</frequency>

    <rootkit_files>etc/shared/rootkit_files.txt</rootkit_files>
    <rootkit_trojans>etc/shared/rootkit_trojans.txt</rootkit_trojans>

    <skip_nfs>yes</skip_nfs>

    <ignore>/var/lib/containerd</ignore>
    <ignore>/var/lib/docker/overlay2</ignore>
  </rootcheck>

</ossec_config>

Where: 

  • check_files enables or disables the checking of files.
  • check_dev enables or disables the checking of /dev.
  • check_ports enables or disables the checking of network ports.
  • check_pids enables or disables the checking of process IDs.
  • check_sys enables or disables checking for anomalous file system objects.
  • check_if enables or disables the checking of network interfaces.
  • frequency option specifies the frequency at which scans are going to be executed (in seconds). In this case, this scan runs every 30 minutes (1800 seconds).
  • rootkit_files specifies the location of the rootkit files database. In this case, the file etc/shared/rootkit_files.txt contains rootkit identifiers and file paths. 

2. Edit the file /var/ossec/etc/shared/rootkit_files.txt to include Koske-specific signatures. This file contains patterns for known rootkit files. The Wazuh Rootcheck module scans the endpoint for matches against these patterns and generates alerts if found:

# Koske Rootkit Signatures
/dev/shm/.hiddenpid     ! Koske rootkit: Hidden PID file for process concealment ::
/etc/ld.so.preload      ! Koske rootkit: Potential LD_PRELOAD manipulation for library hooking ::
/root/.bashrc.koske     ! Koske rootkit: Modified bash config for persistence ::

3. Restart the Wazuh agent to implement changes:

# systemctl restart wazuh-agent

Visualizing the results

When the rootcheck scan runs, we can see that the hidden processes and hidden files are detected by Wazuh, and alerts are generated on the dashboard.

Document details
Document Details

Detecting Koske malware using file hashes in the CDB list

To detect the Koske malware intrusion, you can add its known hashes to a CDB list. The Wazuh File Integrity Monitoring (FIM) module tracks file changes within monitored directories, generating alerts when files are created, modified, or deleted. These alerts include the file’s MD5, SHA1, and SHA256 checksums. By comparing the SHA256 checksums from the FIM module against the entries in the CDB list, Wazuh can identify the presence of the malware.

Follow the steps below to create a CDB list and set up the detection rule.

Wazuh dashboard

Perform the following steps on the Wazuh dashboard to configure the Wazuh server for Koske malware detection.

  1. Navigate to Server management > CDB Lists.
  2.  Click + Add new lists file.
CDB list
  1. Click on + Add new entry and add the malware hashes below as key:value pairs, name the file malware-hashes-koske. Click Save. This is the CDB list that will contain the known file hashes associated with the Koske malware: 
f8c6c873e8289ebbd52d3fc5b6552129d7e20f8a3466aca2d6f1dd2cdd578780:Koske
cf9000db6b026ce350982a7dac67152d1649b0021d4b3f0b2cc7315b254e7161:Koske
ac6d9c301cd098804841e82704257aeb7c9ebb7b3cb2966a1f443ef383238704:Koske
60f4b67f349a13fbf6ef1f45eb6c3147e88015571b2de179e32605e2ead651a0:Koske
  1. Navigate to Server management > Rules.
  2. Click Mange rules files then click on Custom rules.
Mange rules files
  1. Edit the local_rules.xml file and append the custom rule below to trigger alerts on the Wazuh dashboard whenever a SHA256 hash from the CDB list is detected. Click Save
<group name="Koske,">
  <rule id="110111" level="13">
    <if_sid>554, 550</if_sid>
    <list field="sha256" lookup="match_key">etc/lists/malware-hashes-koske</list>
    <description>A known Koske malware hash detected: $(file)</description>
    <mitre>
      <id>T1204.002</id>
    </mitre>
  </rule>
</group>

Note

Rule ID 554 is triggered when a new file is added to a monitored directory, while rule ID 550 is triggered when a file is modified.

local rules xml
  1. Navigate to Server management > Settings.
  2. Click on Edit configuration to edit the /var/ossec/etc/ossec.conf file and include the etc/lists/malware-hashes-koske list within the <ruleset> configuration block. Click Save.
  <list>etc/lists/malware-hashes-koske</list> 
Configuration
Manager Configuration
  1. Click Restart Manager to apply the changes. Click Confirm when prompted.
Manager Configuration

Ubuntu endpoint

  1. Monitor the Downloads folder of the current user in real-time by adding the below configuration within the <syscheck> block of the /var/ossec/etc/ossec.conf file. Replace <USER> with the correct username of the directory.
<directories realtime="yes">/home/<USER>/Downloads</directories>

Note

In this blog post, we monitored only the Downloads folder of the current user. However, you can configure Wazuh to monitor any directory of your choice. 

  1. Restart the Wazuh agent to apply the changes:
# systemctl restart wazuh-agent

Visualizing the results

Perform the following on the Wazuh dashboard to view the alerts generated when the Koske malware files are added to the download directory.

  1. Navigate to Threat intelligence > Threat Hunting and click the Events tab
  2. Click + Add filter. Then filter by rule.id.
  3. In the Operator field, select is .
  4. Search and select 110111 in the Values field.
  5. Click Save.
events

Conclusion

This blog post demonstrated how to detect Koske malware activities on monitored Linux endpoints. We enriched logs from the victim endpoint by integrating SysmonForLinux and created Wazuh detection rules to identify malicious behavior associated with the Koske malware. We also leveraged Wazuh Rootcheck module to scan for Koske malware footprints and utilized a CDB list containing known Koske malware hashes to confirm its presence.

Wazuh is a free, open source security platform that provides a wide range of capabilities to monitor and safeguard your infrastructure against malicious activities. If you have any questions about this blog post or Wazuh, we invite you to join our community, where our team is available to assist you.

References