Kimsuky's Five-Stage GrimResource Loader: When an MMC File Becomes a Shellcode Injector
TL;DR: A Kimsuky (APT43/Velvet Chollima) operation was caught deploying a five-stage loader chain that begins with a single .msc file -- a Microsoft Management Console configuration file -- and ends with 1MB of x86 shellcode injected directly into memory. The chain uses the GrimResource technique for initial execution, then cascades through JScript decryption, .NET BinaryFormatter deserialization (SortedSet gadget chain), XAML-based shellcode injection via XamlReader.Parse(), and finally native code execution via Marshal.GetDelegateForFunctionPointer(). The C2 infrastructure spans 14 IP addresses across UCloud HK (AS135377) and DAOU Technology (AS45996) in Seoul, Korea, using three dynamic DNS providers (dynv6.net, dns.army, v6.navy) for domain rotation. Every C2 server runs the same Apache 2.4.58 / PHP 8.0.30 / OpenSSL 3.1.3 stack -- a deployment fingerprint so uniform it could only come from automated provisioning.
A .msc File Walks Into a Network
When most security teams think about dangerous file types, .msc files do not make the list. They should.
Microsoft Management Console files are XML documents that configure administrative snap-ins -- things like Device Manager, Disk Management, Event Viewer. They are opened by mmc.exe, a signed Microsoft binary. They are generally trusted by email gateways, endpoint protection, and users alike.
Which is exactly why Kimsuky chose them.
The sample -- plugin.msc, 5.9MB, uploaded to MalwareBazaar from Hungary on March 11, 2026 -- is a valid MMC Console File that opens normally in mmc.exe. What the user does not see is the XSL Transform payload embedded in the XML StringTable node, wrapped in a unescape() call. When the MMC engine processes the stylesheet, it hits a <ms:script> block containing JScript, and the chain begins.
This is the GrimResource technique, and Kimsuky has made it their own.
What Was Found vs. What Was Known
| Aspect | Prior Reporting | Our Findings |
|---|---|---|
| Delivery mechanism | Kimsuky known for HWP, DOC, CHM lures | .msc file using GrimResource technique |
| Execution chain depth | Typical 2-3 stage loaders | 5 distinct stages: MSC β JScript β .NET deserialization β XAML β shellcode |
| Obfuscation | Standard encoding/encryption | Custom BUxBF cipher (split-alphabet Base64 + XOR), reversed base64 with space padding, compressed shellcode |
| Gadget chain | Known .NET exploitation | SortedSet/TypeConfuseDelegate chain redirecting Compare() to XamlReader.Parse() |
| C2 infrastructure | Seoul-based hosting | 14 IPs across 2 ASNs (UCloud HK, DAOU Technology), uniform Apache/PHP/OpenSSL stack |
| Domain rotation | Free DNS services | Three providers simultaneously: dynv6.net, dns.army, v6.navy |
| Related samples | .msc files increasingly popular | Campaign cluster: Smart_Policing, Cyber-Advisory, Scheme_Application_Form |
The Five-Stage Execution Chain
[Stage 1: plugin.msc]
MMC Console File (XML, 5.9MB)
GrimResource: XSL Transform in StringTable
SW_HIDE flag β window hidden on launch
|
v
[Stage 2: JScript Loader]
BUxBF() decryption function
Split-alphabet Base64 + XOR cipher
Unique hex key per encrypted call
Forces .NET 4.0 CLR via COMPLUS_Version
Builds 1.1MB payload via 50+ string concatenations
|
v
[Stage 3: .NET Deserialization]
BinaryFormatter.Deserialize()
SortedSet<string> gadget chain
TypeConfuseDelegate trick:
String.Compare() β XamlReader.Parse()
1,138,987-byte serialized object
|
v
[Stage 4: XAML Shellcode Injection]
ResourceDictionary (1,136,894 bytes)
Base64 decode β GZip decompress β 1,004,940 bytes
VirtualAlloc (PAGE_READWRITE)
Marshal.Copy (write shellcode)
VirtualAlloc (PAGE_EXECUTE_READ)
Marshal.GetDelegateForFunctionPointer()
|
v
[Stage 5: x86 Shellcode]
1,004,940 bytes of MSVC-compiled native code
Anti-analysis prologue (xor eax; sub; inc; jz)
XOR key 0xACC2D51A for config decryption
Runtime API resolution: Sleep, VirtualAllocEx, CreateThread
C2 communication (encrypted at runtime)
Every stage is designed to evade a different layer of defense. Stage 1 bypasses file type restrictions. Stage 2 evades static string detection. Stage 3 exploits trusted .NET deserialization. Stage 4 uses XAML -- a UI markup language -- to inject shellcode. Stage 5 encrypts its own configuration to prevent network IOC extraction.
Stage 2: The BUxBF Decoder in Detail
The JScript embedded in the XSL stylesheet is not just obfuscated -- it uses a custom encryption scheme:
| Encrypted Call | Decrypted Value | Purpose |
|---|---|---|
| BUxBF(0) | WScript.Shell | Shell object creation |
| BUxBF(1) | Process | Environment access |
| BUxBF(2) | COMPLUS_Version | .NET CLR version forcing |
| BUxBF(3) | v4.0.30319 | Specific CLR version |
| BUxBF(4) | System.Text.ASCIIEncoding | Text encoding |
| BUxBF(5) | System.Security.Cryptography.FromBase64Transform | Base64 decode |
| BUxBF(6) | System.IO.MemoryStream | Memory stream |
| BUxBF(7/8) | System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | Deserialization engine |
The algorithm works in two passes: gZKYc() performs custom Base64 decoding using a split alphabet (the Base64 character table is fragmented across the code to evade signature-based detection), then rkTNf() applies XOR decryption using a repeating key unique to each call. Each invocation of BUxBF() uses a different hex key -- for example, 023389aee2418eee -- ensuring that no two decrypted strings share a key.
The critical trick is the COMPLUS_Version environment variable set to v4.0.30319. This forces the .NET 4.0 CLR to load, which is required for the deserialization gadget chain in Stage 3. Without this, the payload would fail on systems with newer .NET versions as the default runtime.
The payload itself -- variable XGMtr -- is assembled through 50+ string concatenations with deliberate space characters inserted between chunks. The spaces are stripped, the string is reversed, Base64-padded, and decoded, producing a 1,138,987-byte .NET BinaryFormatter serialized object.
Stage 3: The Gadget Chain
The deserialization payload uses the SortedSet/TypeConfuseDelegate gadget chain -- a technique from the ysoserial.net toolkit:
BinaryFormatter.Deserialize()
β System.Collections.Generic.SortedSet<string>
β System.DelegateSerializationHolder
β Comparison<string> delegate
β System.Windows.Markup.XamlReader.Parse(string)
The elegance is in the type confusion. SortedSet<string> needs a Comparison<string> delegate to sort its elements. The gadget chain replaces the legitimate comparison delegate with one that calls XamlReader.Parse() instead of String.Compare(). When the SortedSet is deserialized and tries to sort its contents, it does not compare strings -- it parses one of them as XAML.
This effectively turns the .NET framework against itself. The entire chain runs inside trusted .NET code. No PowerShell. No cmd.exe. No suspicious process creation.
Stage 4: XAML as a Weapon
The XAML ResourceDictionary (1,136,894 bytes) performs in-memory shellcode injection using only .NET framework classes:
Convert.FromBase64String()decodes 849,582 bytes of compressed dataGZipStreamdecompresses to 1,004,940 bytes of x86 shellcodeVirtualAlloc(IntPtr.Zero, size, 0x1000, 0x04)allocates memory as PAGE_READWRITEMarshal.Copy()writes the shellcode into allocated memoryVirtualAlloc(ptr, size, 0x1000, 0x20)changes protection to PAGE_EXECUTE_READMarshal.GetDelegateForFunctionPointer()+Action.Invoke()executes
The memory protection change from RW to RX (rather than allocating as RWX directly) is a deliberate evasion technique. Many EDR products flag single-step PAGE_EXECUTE_READWRITE allocations but are less likely to alert on a two-step RWβRX transition.
The XAML itself uses Microsoft.VisualBasic.Interaction.CallByName() for API resolution -- another layer of indirection that avoids direct import references.
The C2 Empire: 14 Servers, 3 DNS Providers, 1 Fingerprint
C2 IP Addresses
| IP Address | Port | ASN | Organization |
|---|---|---|---|
| 118[.]194[.]248[.]246 | 443 | AS135377 | UCloud HK |
| 152[.]32[.]243[.]178 | 80 | AS135377 | UCloud HK |
| 101[.]36[.]114[.]66 | 80 | β | Korea |
| 27[.]102[.]137[.]140 | 80 | AS45996 | DAOU Technology |
| 152[.]32[.]138[.]146 | 443 | AS135377 | UCloud HK |
| 118[.]194[.]248[.]134 | 443 | AS135377 | UCloud HK |
| 118[.]193[.]69[.]19 | 443 | AS135377 | UCloud HK |
| 152[.]32[.]139[.]149 | 80 | AS135377 | UCloud HK |
| 167[.]88[.]166[.]204 | 443 | β | β |
| 118[.]194[.]248[.]183 | 80 | AS135377 | UCloud HK |
| 152[.]32[.]243[.]215 | 80 | AS135377 | UCloud HK |
| 27[.]102[.]138[.]125 | β | AS45996 | DAOU Technology |
| 118[.]194[.]249[.]109 | β | AS135377 | UCloud HK |
| 101[.]36[.]114[.]231 | 443 | β | β |
Fourteen C2 servers. Nine on UCloud HK (AS135377), two on DAOU Technology (AS45996), three unattributed. Nearly all resolve to Seoul, South Korea. UCloud HK is a Hong Kong-registered subsidiary of UCloud, a Chinese cloud provider -- frequently observed in North Korean APT infrastructure due to its presence in South Korean data centers.
The Uniform Server Stack
Every C2 server presents the same fingerprint:
| Component | Version |
|---|---|
| Web Server | Apache 2.4.58 |
| PHP | 8.0.30 (EOL) |
| OpenSSL | 3.1.3 |
| TLS | Self-signed certificates |
| Additional | RDP (3389) exposed on some |
This level of uniformity across 14 servers is not manual configuration. It is automated provisioning -- likely a deployment script or container image that Kimsuky rolls out to each new C2 node. The use of PHP 8.0.30 (end-of-life since November 2023) as a consistent choice further suggests a frozen deployment template that has not been updated.
Dynamic DNS Rotation
| Provider | Domains | Purpose |
|---|---|---|
| dynv6.net | link-nid-log.*.dynv6.net, elecviews85.dynv6.net, mhjjh.dynv6.net | C2 domain rotation |
| dns.army | ndocs0link.dns.army, 3tg8i.dns.army | C2 domain rotation |
| v6.navy | a7f3q.v6.navy | C2 domain rotation |
Three free dynamic DNS providers used simultaneously. If one is blocked or taken down, the others continue operating. The subdomain patterns (link-nid-log, elecviews85, a7f3q) appear randomly generated, suggesting automated domain creation.
Attribution: Kimsuky / APT43
The attribution chain:
- YARA match:
Detect_Kimsuky_APT_Malware(by daniyyell) on MalwareBazaar - ThreatFox tags: All C2 IOCs tagged
win.kimsukywith first_seen dates March 1-10, 2026 - GrimResource adoption: Consistent with Kimsuky's Q1 2026 TTP evolution
- Infrastructure pattern: Seoul-based hosting via UCloud HK and DAOU Technology matches known Kimsuky preferences
- Dynamic DNS: Free DNS services for C2 rotation is a long-documented Kimsuky behavior
- Related MSC samples: Campaign cluster includes
Smart_Policing_Industry_Participation.msc,Cyber-Advisory-2026.pdf.msc-- government/policy themes consistent with Kimsuky targeting
MITRE ATT&CK Mapping
| Tactic | Technique | ID | Implementation |
|---|---|---|---|
| Initial Access | Spearphishing Attachment | T1566.001 | .msc file delivered via email |
| Execution | System Binary Proxy Execution | T1218 | mmc.exe processes XSL transform |
| Execution | XSL Script Processing | T1220 | GrimResource XSL in StringTable |
| Execution | JScript | T1059.007 | BUxBF decoder in XSL stylesheet |
| Defense Evasion | Obfuscated Files | T1027 | BUxBF cipher, reversed base64, space padding |
| Defense Evasion | Deobfuscate/Decode | T1140 | Multi-stage decoding chain |
| Defense Evasion | Process Injection | T1055 | VirtualAlloc + shellcode copy + execute |
| Defense Evasion | Trusted Developer Utilities | T1127 | .NET BinaryFormatter deserialization |
| C2 | Dynamic DNS | T1568.001 | dynv6.net, dns.army, v6.navy |
| C2 | Web Protocols | T1071.001 | HTTPS/HTTP to C2 servers |
Detection
Endpoint
- Block
.mscfile execution via Group Policy or AppLocker - Monitor for
mmc.exespawning script engines (JScript, VBScript) - Alert on
COMPLUS_Versionenvironment variable modifications - Detect
VirtualAllocwith PAGE_READWRITE followed by PAGE_EXECUTE_READ protection changes from non-standard processes - Monitor for
XamlReader.Parse()calls outside legitimate WPF applications
Network
- Block all 14 listed C2 IPs
- Sinkhole
*.dynv6.net,*.dns.army,*.v6.navyat DNS resolver level (or specific domains if blanket block is too broad) - Alert on connections to UCloud HK (AS135377) from enterprise networks
- Monitor for self-signed Apache/PHP stacks on non-standard ports
File
Deploy YARA rules targeting:
- GrimResource MSC pattern (XSL Transform in StringTable)
- BUxBF decoder function signature
- SortedSet/TypeConfuseDelegate gadget chain
- XAML shellcode injection pattern
- ConsoleFileID
1225e4fe-5c83-4d7f-a643-606b18e5f890
Indicators of Compromise
File Indicators
# MSC Loader
SHA256: f239e3fedc4926ff3cf58f95bacff9d8f11289e58036ed507ab3f435dce1b2b1
MD5: 6db53d66629f95a2d830a4f56e8c69f2
SHA1: 253d232e1485e7e60ff3380999412c773d0a9a14
File: plugin.msc
# Extracted Shellcode
SHA256: 95f4954ad79fa972bfd4fe217608ed5216c674e8ae6662cb8ffb31dbed50ec63
MD5: 66126fa42accfb183f72e25b20750b97
# MMC Console File ID
1225e4fe-5c83-4d7f-a643-606b18e5f890
Network Indicators
# C2 Domains (defanged)
ndocs0link[.]dns[.]army
a7f3q[.]v6[.]navy
3tg8i[.]dns[.]army
link-nid-log[.]oq7n2[.]dynv6[.]net
link-nid-log[.]oc9bk[.]dynv6[.]net
elecviews85[.]dynv6[.]net
mhjjh[.]dynv6[.]net
# C2 IPs (defanged)
118[.]194[.]248[.]246 (UCloud HK)
152[.]32[.]243[.]178 (UCloud HK)
101[.]36[.]114[.]66 (Korea)
27[.]102[.]137[.]140 (DAOU Technology)
152[.]32[.]138[.]146 (UCloud HK)
118[.]194[.]248[.]134 (UCloud HK)
118[.]193[.]69[.]19 (UCloud HK)
152[.]32[.]139[.]149 (UCloud HK)
167[.]88[.]166[.]204
118[.]194[.]248[.]183 (UCloud HK)
152[.]32[.]243[.]215 (UCloud HK)
27[.]102[.]138[.]125 (DAOU Technology)
118[.]194[.]249[.]109 (UCloud HK)
101[.]36[.]114[.]231
C2 URLs
hxxps://ndocs0link[.]dns[.]army/?naps
hxxps://a7f3q[.]v6[.]navy/
hxxps://3tg8i[.]dns[.]army/
hxxp://link-nid-log[.]oq7n2[.]dynv6[.]net/
hxxp://link-nid-log[.]oc9bk[.]dynv6[.]net/
hxxps://elecviews85[.]dynv6[.]net/?naps
hxxps://mhjjh[.]dynv6[.]net/
Recommended Actions
Immediate
- Block all 14 C2 IPs and 7 C2 domains at network perimeter
- Deploy YARA and Suricata rules from the investigation
- Block
.mscfile attachments in email gateways - Hunt for
mmc.exespawning script engines in EDR telemetry
Short-Term
- Submit abuse reports to UCloud HK (
hegui@ucloud[.]cn) and DAOU Technology (infra-tech@daou[.]co[.]kr) - Block dynamic DNS providers at DNS resolver level
- Monitor for
COMPLUS_Versionenvironment variable modifications across the fleet - Assess exposure to GrimResource technique across all .msc file handling
Strategic
- Disable XSL processing in MMC where possible
- Implement application control policies restricting
mmc.exeto approved .msc files - Alert on BinaryFormatter deserialization in non-standard contexts
- Track Kimsuky's Q1 2026 .msc campaign cluster for new samples
Published by Breakglass Intelligence. Investigation conducted 2026-03-11. One .msc file. Five execution stages. Fourteen C2 servers. A North Korean APT that turned an admin tool into a shellcode injector. Classification: TLP:CLEAR