GoSerpent Malware Internals: Reversing the Undocumented Go Implant Silently Espionaging Southeast Asian Governments Since Late 2025
Somebody sat inside a Southeast Asian foreign ministry for the better part of a year, quietly copying Word docs and PDFs into an encrypted archive, and never once phoned that archive straight home. They staged it. They waited. Then they walked the data out over an authenticated network share. That is not smash-and-grab crime. That is intelligence work, and GoSerpent, the Go-based implant Kaspersky’s GReAT team detailed in July 2026, is the tooling built to do it patiently. This post pulls the binary apart: the Go runtime you have to fight through, the AES-CBC argument that hides the C2, the ChaCha20 channel, the svchost injection, and the detection content that actually catches it.
Why Go, and why this campaign matters
Here is my thesis up front: Go has become the default language for a certain tier of state-nexus espionage implant, and GoSerpent is a near-textbook example of why. A statically-linked Go binary drops a full runtime, garbage collector, and scheduler into a single fat executable that runs on Windows and Linux with the same source. The stripped binary buries the operator’s actual logic under tens of thousands of runtime.* functions. Antivirus heuristics tuned on decades of C/C++ malware get noisy and unreliable against it. For an actor who cares about dwell time over speed, that trade is worth it.
The catch, and this is the part defenders should internalize, is that Go’s runtime is also the biggest gift the analyst gets. The pclntab (program counter line table) structure that the Go runtime needs for stack traces and panics carries function and package names even in “stripped” builds. Mandiant’s GoReSym exists precisely because that metadata is nearly impossible to fully erase without breaking the binary. So the same feature that frustrates signature engines hands the reverse engineer a symbol table on a plate. GoSerpent’s authors knew this, which is why the interesting secrets (the C2 address, the channel password) live outside the binary, delivered as an encrypted launch argument.
Kaspersky discovered the campaign in February 2026, traced first activity back to late 2025, and watched the toolset evolve through a fresh second-stage push in May 2026. Victims are government and diplomatic entities across Southeast Asia. The operators overlap, by Kaspersky’s read, with TetrisPhantom, the APAC-focused actor first documented in October 2023 for spying on government bodies through hardware-encrypted USB drives. Attribution is not nailed down, and I will not pretend otherwise, but the targeting and tradecraft rhyme.
Campaign timeline and operational posture
| Phase | Timeframe | What happened |
|---|---|---|
| Initial foothold | Late 2025 | GoSerpent RAT deployed as lass.exe / updates.exe, encrypted argument delivers C2 |
| Reconnaissance dwell | Days after foothold | Operators sit idle several days before staging additional components |
| Collection | Late 2025 onward | ThumbcacheService DLL harvests Office docs and PDFs into thumbcache_605a.db |
| Credential access | Ongoing | Mimikatz against LSASS, QuarksDumpLocalHash against the SAM hive |
| Kaspersky discovery | February 2026 | Campaign identified and tracked |
| Toolset evolution | May 2026 | Stowaway RAT and the TmcLoader/TmcPayload exfiltration chain introduced |
| Exfiltration | After months of staging | Archives moved over authenticated network shares, not direct C2 uploads |
The single most important behavioural fact in that table is the last row. The threat actor deliberately avoided pushing collected data out through the C2 channel. They staged locally, harvested credentials to reach file servers, and used legitimate SMB access to move the archives. That decision starves most network-centric DLP and C2 detection of the signal it depends on. If your entire detection strategy is “watch for large outbound uploads to weird IPs,” GoSerpent walks right past you.
Reversing the GoSerpent RAT: fighting the runtime
Set up a throwaway Windows 10/11 analysis VM with no route to the internet. Load Ghidra 11.x, and before you touch the disassembler, run the binary through GoReSym.
GoReSym.exe -t -d -p goserpent_sample.exe > symbols.json
The -t flag pulls type information, -d recovers the standard-library package metadata, and -p extracts user package paths. GoReSym walks the pclntab (whose header magic for Go 1.20 and later is 0xFFFFFFF1, laid out little-endian as the bytes F1 FF FF FF 00 00) and reconstructs the function name table. Feed the resulting JSON into a Ghidra script to relabel functions, and a wall of FUN_00401a20 blobs turns into main.decryptArgs, main.deriveKey, crypto/aes.NewCipher, and so on.
That relabelling is the whole game. In a C++ implant you would grind through call graphs guessing at intent. Here, once symbols land, you can go straight to the function you care about. Search the recovered symbol tree for anything under main.* (the malware author’s own package) and for the crypto primitives: crypto/aes, crypto/sha256, and golang.org/x/crypto/chacha20.
A word on Go runtime noise. You will see runtime.morestack_noctxt prologues on nearly every function, goroutine stack-growth checks, and the scheduler machinery. Ignore it. It is boilerplate the compiler emits. The analyst-relevant logic is a rounding error in code volume compared to the runtime, and the symbol names tell you exactly which few hundred bytes matter.
Argument decryption and the C2 handshake
GoSerpent does not carry its C2 address in plaintext, or in any static form the strings tool would reveal. Instead it expects an encrypted, Base64-encoded command-line argument at launch. That argument, once decrypted, contains the C2 server address and a communication password. This is smart operational hygiene: an implant captured on disk without its launcher gives up almost nothing about the infrastructure.
The decryption is AES in CBC mode with a fixed IV of 31323334353637383930616263646566 (that hex is the ASCII string 1234567890abcdef) and keys derived from predefined strings baked into the binary. Reconstructed, the routine looks like this:
func decryptArgs(b64Arg string, key []byte) ([]byte, error) {
ct, err := base64.StdEncoding.DecodeString(b64Arg)
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key) // key from predefined in-binary string
if err != nil {
return nil, err
}
iv := []byte("1234567890abcdef") // fixed IV: 31323334...61626364656 6
mode := cipher.NewCBCDecrypter(block, iv)
pt := make([]byte, len(ct))
mode.CryptBlocks(pt, ct)
return pkcs7Unpad(pt), nil // yields "c2_addr:port|password"
}
A fixed IV in CBC is a genuine cryptographic weakness (it makes identical plaintext blocks under the same key produce identical ciphertext), but the operators do not care because this is obfuscation, not confidentiality against a cryptanalyst. It exists to defeat a lazy strings pass, and it does.
Once the password is in hand, GoSerpent derives its ChaCha20 session key from the SHA-256 hash of that password:
key := sha256.Sum256([]byte(password)) // 32-byte ChaCha20 key
// stream, _ := chacha20.NewUnauthenticatedCipher(key[:], nonce)
In the lab, set an x64dbg breakpoint right after the CBC decrypt call. When it fires, dump the plaintext buffer to read the C2 address and password in memory, then let the SHA-256 derivation run and grab the 32-byte key. With key and address recovered, stand up a Python listener that speaks the same ChaCha20 framing and replay the handshake while Wireshark captures it. That capture is what you turn into network signatures later.
The operator’s post-handshake capability set is broad and modular: open a command shell, upload and download files, pivot to additional systems, bind a listener, forward traffic between hosts, and flip the compromised machine into a SOCKS5 proxy. That last capability is central to the tradecraft. By tunnelling through infected government hosts, the actor keeps its real infrastructure off the wire and enables lateral movement deeper into networks that trust those hosts.

The collection toolchain: ThumbcacheService, Mimikatz, QuarksDumpLocalHash
After the days-long dwell, the operators bring in collection. ThumbcacheService is a DLL that registers as a Windows service and supplements GoSerpent with a proper file-hunting engine. It walks the filesystem for .docx, .xlsx, and .pdf files, monitors deleted documents (a nice touch for catching things users tried to clean up), and packs its loot into encrypted archives.
The staging artefact is where this gets catchable. Everything lands at:
C:\Users\Public\thumbcache_605a.db
There is no legitimate reason for a file named thumbcache_605a.db to sit in C:\Users\Public\. Real thumbnail cache databases live under each user’s AppData\Local\Microsoft\Windows\Explorer\. The name is chosen to look boring in a directory listing, and it works on a human eyeballing the disk. It does not survive a rule that knows the correct path.
Credential access rides alongside collection with two well-known tools:
| Tool | Target | Technique |
|---|---|---|
| Mimikatz | LSASS process memory | Dumps credential material (T1003.001) |
| QuarksDumpLocalHash | SAM registry hive | Extracts local account password hashes (T1003.002) |
Neither is novel, and that is the point. State actors reach for commodity credential tools constantly because they work and because a Mimikatz hit muddies attribution. The SAM extraction matters specifically because it yields local hashes the operators can use to authenticate to those network shares later, closing the loop on the exfiltration plan.
Stage 2: Stowaway, TmcLoader, and the injected payload
In May 2026 the operators upgraded. Stowaway is a weaponised build of a publicly available Go proxy framework, customised to make the infection stealthier. It talks over TCP, HTTP, or WebSocket, wraps traffic in AES-256-GCM or TLS, and offers SOCKS5 proxies, port forwarding, reverse tunnels, remote shells, and file transfers. Its real value to the operator is topology: Stowaway has both admin and agent roles, so the actor can build chained proxy paths across multiple compromised hosts, hopping deeper into segmented networks while every leg looks like ordinary internal traffic.
The exfiltration muscle is TmcLoader, a C++ service, paired with its embedded TmcPayload. TmcLoader carries an encrypted payload in its PE .data section, decrypts it at runtime, and injects it directly into the memory space of a legitimate svchost.exe. Fileless second stage, no payload ever written to disk in decrypted form.
Two implementation details are worth reversing carefully:
API obfuscation via circular XOR. TmcLoader hides its Windows API names from static analysis by XORing each byte of an obfuscated name against the value of the following byte, then Base64-encoding the result. Decoded and resolved at runtime, the API names never appear as clean strings. The decode loop looks like this:
// circular XOR: each byte XORed with the next byte's value
void decode_api(unsigned char *buf, size_t len) {
for (size_t i = 0; i + 1 < len; i++) {
buf[i] ^= buf[i + 1];
}
// buf now holds a Base64 string; Base64-decode, then
// pass to GetProcAddress via resolved GetModuleHandle
}
This is why a naive import-table or string scan for CreateRemoteThread or VirtualAllocEx comes up empty. You have to find the decode stub, run it in a debugger or reimplement it, and recover the API list dynamically.
Config polling and share-based exfil. TmcPayload builds the path:
C:\Users\Public\Libraries\{BBF061R2-BE25-4F6D-8B2D-1A6A39C3FSA2}.db
It checks for that configuration file. If it is absent, the payload sleeps for a random interval and rechecks, rather than failing loudly or beaconing. That randomised delay is deliberate anti-analysis: in a sandbox with a short timeout, the payload does nothing observable and gets scored clean. When the config does exist, it contains encrypted network-share credentials and destination paths. The payload then moves the staged archives out over authenticated SMB. No direct C2 upload, no anomalous egress to a suspicious IP, just an internal file copy that blends into normal Windows behaviour.

Anti-analysis and evasion, catalogued
Pulling the tradecraft together, GoSerpent layers evasion at every stage rather than betting on a single trick:
- No secrets in the binary. C2 and password arrive as an encrypted launch argument, so a captured sample leaks nothing about infrastructure.
- Layered crypto for obfuscation. AES-CBC on the argument, ChaCha20 on the channel, AES-256-GCM/TLS on Stowaway. All chosen to defeat string and signature scanning, not to stop a cryptographer.
- Name masquerading.
lass.exe,updates.exe,thumbcache_605a.db, ThumbcacheService, all borrow the visual grammar of legitimate Windows components. - Fileless second stage. TmcLoader decrypts and injects into
svchost.exe, leaving no cleartext payload on disk. - Sandbox-resistant timing. TmcPayload’s randomised recheck loop stalls automated analysis.
- Legitimate cloud C2. Infrastructure sits on Alibaba Cloud and UCLOUD HK, so beacon traffic blends with the enormous volume of legitimate connections to those providers.
- Decoy key material. Strings like
www.microsoft.com,www.spacex.com, andgithub.codeare embedded as secret-key material, a small piece of misdirection for an analyst grepping for indicators. - Exfil over trusted channels. SMB shares instead of C2 uploads sidesteps network exfil detection entirely.
None of these is exotic on its own. Stacked, and executed by an operator willing to wait months, they produce an implant that stays quiet.

Comparative attribution: GoSerpent, BRICKSTORM, and GopherWhisper
I want to be careful here, because attribution is where good technical writeups go to die. Kaspersky’s overlap call is with TetrisPhantom, and it is explicitly unconfirmed. The comparison below to BRICKSTORM and GopherWhisper is my own analytical exercise in the Go-implant pattern space, not a claim that these are the same crew.
| Attribute | GoSerpent | BRICKSTORM (UNC5221) | GopherWhisper |
|---|---|---|---|
| Language | Go | Go | Go (+ C++ loaders) |
| Disclosed by | Kaspersky GReAT, Jul 2026 | Mandiant/Google 2024; NVISO 2025 | ESET, Apr 2026 |
| Attribution | TetrisPhantom overlap (unconfirmed) | China-nexus cluster UNC5221 | China-aligned (ESET) |
| Primary targets | SEA government/diplomatic | European industry; US tech/legal | Mongolian government |
| C2 transport | ChaCha20 over TCP | WebSocket, DNS-over-HTTPS resolution | LoLS via Slack/Discord/Outlook |
| Platform reach | Windows (Go cross-compilable) | Windows and Linux variants | Windows |
| Dwell posture | Months, staged exfil | Very long dwell, appliance-focused | Patient, low-noise |
What these share is a pattern, not a fingerprint. All three chose Go for cross-platform reach and signature evasion. All three lean on infrastructure that blends in, whether that is cloud-provider hosting, DNS-over-HTTPS, or living-off-legitimate-services C2 through Slack and Outlook. All three prioritise dwell over speed. That convergence tells you something real about the current China-nexus toolkit meta, even if it tells you nothing definitive about which desk built GoSerpent. Treat the TetrisPhantom link as the working hypothesis Kaspersky offers, and treat the broader pattern as context, not proof.
YARA: fingerprinting the runtime and the unique artefacts
Two detection ideas drive these rules. First, gate on the Go pclntab magic to confirm you are looking at a Go binary. Second, require a couple of GoSerpent-specific string artefacts so you are not flagging every Go program on the planet.
rule GoSerpent_Backdoor_GoRuntime_Strings {
meta:
description = "GoSerpent backdoor: Go runtime + unique string constants"
author = "GenXCyber Research"
reference = "securelist.com/goserpent-backdoor-in-southeast-asia/120687/"
date = "2026-07-22"
strings:
// Go 1.20+ pclntab header magic (0xFFFFFFF1, LE) + pad
$go_pclntab = { F1 FF FF FF 00 00 }
// Fixed AES-CBC IV, ASCII "1234567890abcdef"
$aes_iv = "31323334353637383930616263646566" ascii wide
$thumb_db = "thumbcache_605a.db" ascii wide
$tmc_guid = "BBF061R2-BE25-4F6D-8B2D-1A6A39C3FSA2" ascii wide
$staging = "C:\\Users\\Public\\Libraries\\" ascii wide
$decoy1 = "www.spacex.com" ascii wide
$decoy2 = "github.code" ascii wide
$mask1 = "lass.exe" ascii wide
$mask2 = "updates.exe" ascii wide
condition:
uint16(0) == 0x5A4D and $go_pclntab and
2 of ($aes_iv, $thumb_db, $tmc_guid, $staging, $decoy1, $decoy2, $mask1, $mask2)
}
For TmcLoader, the injection target and the config path anchor the rule, and the circular-XOR decode stub is the strongest discriminator once you extract its exact byte pattern from a confirmed sample:
rule TmcLoader_CXX_Injector {
meta:
description = "TmcLoader: svchost injection + XOR API-name obfuscation"
author = "GenXCyber Research"
reference = "securelist.com/goserpent-backdoor-in-southeast-asia/120687/"
strings:
$svchost = "svchost.exe" ascii wide nocase
$config_path = "C:\\Users\\Public\\Libraries\\" ascii wide
$svc_install = "CreateServiceW" ascii wide
// Extract the exact circular-XOR loop bytes from a confirmed
// TmcLoader sample and add as a hex string here.
condition:
uint16(0) == 0x5A4D and $svchost and $config_path and $svc_install
}
And a rule for the Stowaway Stage 2 build, keyed on the framework package names GoReSym recovers plus its GCM/WebSocket transport strings:
rule Stowaway_Go_RAT_Customized {
meta:
description = "Customized Stowaway RAT used in GoSerpent Stage 2"
author = "GenXCyber Research"
strings:
$go_pclntab = { F1 FF FF FF 00 00 }
$pkg1 = "Stowaway" ascii wide
$pkg2 = "socks5" ascii wide
$aes_gcm = "NewGCM" ascii wide
$ws = "websocket" ascii wide nocase
condition:
uint16(0) == 0x5A4D and $go_pclntab and
2 of ($pkg1, $pkg2, $aes_gcm, $ws)
}
Verify every string marked for confirmation against real samples from Kaspersky’s IOC release before you deploy these in production. The fixed IV and the config GUID path are sourced directly from Securelist and are solid. The XOR stub bytes you must lift yourself.

Detection and defense
Telemetry to have on
Sysmon does most of the heavy lifting here, provided you have a decent config and the right audit policies enabled underneath it.
| Sysmon Event | Signal |
|---|---|
| EID 1 (Process Create) | lass.exe/updates.exe from odd parents; svchost.exe with service-atypical command lines |
| EID 3 (Network Connect) | Beacons to Alibaba Cloud / UCLOUD HK ASNs (45102, 59019) from government hosts; SOCKS5 negotiation from non-browser images |
| EID 7 (Image Load) | Unexpected DLL loaded into svchost.exe matching ThumbcacheService naming |
| EID 11 (File Create) | Creation of C:\Users\Public\thumbcache_605a.db or C:\Users\Public\Libraries\*.db |
| EID 13 (Registry Set) | New service key with a DLL ImagePath outside System32/SysWOW64 |
| EID 25 (Process Tampering) | Hollowing/injection events targeting svchost.exe |
Pair that with the Microsoft-Windows-Security-Auditing provider for LSASS handle access (Event 4656/4663) and SAM hive reads (4663), and Microsoft-Windows-Kernel-Process for injection heuristics.
Sigma rules
title: SOCKS5 Proxy Negotiation by Non-Browser Process
logsource:
product: windows
category: network_connection
detection:
selection:
Initiated: 'true'
DestinationPort: 1080
Image|endswith:
- '\updates.exe'
- '\lass.exe'
condition: selection
level: high
tags: [attack.command_and_control, attack.t1090.001]
title: Staging DB File Created in Public Libraries
logsource:
product: windows
category: file_event
detection:
selection:
TargetFilename|startswith: 'C:\Users\Public\Libraries\'
TargetFilename|endswith: '.db'
condition: selection
level: high
tags: [attack.collection, attack.t1074.001]
title: Service Registered with DLL ImagePath Mimicking System Component
logsource:
product: windows
category: registry_set
detection:
selection:
TargetObject|contains: '\CurrentControlSet\Services\'
TargetObject|endswith: '\ImagePath'
Details|endswith: '.dll'
filter_legit:
Details|contains: ['\System32\', '\SysWOW64\']
condition: selection and not filter_legit
level: high
tags: [attack.persistence, attack.t1543.003]
Hunting queries
Because GoSerpent exfiltrates over SMB rather than C2, hunt the collection and staging behaviour, not the egress. A KQL starting point for Microsoft Defender:
DeviceFileEvents
| where FolderPath startswith @"C:\Users\Public\"
| where FileName in~ ("thumbcache_605a.db")
or FolderPath startswith @"C:\Users\Public\Libraries\"
| project Timestamp, DeviceName, InitiatingProcessFileName, FolderPath, FileName
Then pivot on any process that both touched those paths and later authenticated to a file server, which is the exfiltration tell.
Hardening that actually blunts this chain
- Turn on Credential Guard and LSA Protection (PPL) to break the Mimikatz LSASS dump.
- Restrict
SeDebugPrivilegeto SYSTEM via Group Policy. - Enforce WDAC or AppLocker to block unsigned DLL service registration.
- Apply egress filtering on government workstations against Alibaba Cloud / UCLOUD HK ranges; a diplomatic endpoint has no business beaconing there.
- Drop canary files named
thumbcache_605a.dbinC:\Users\Public\and alert on any read. GoSerpent will walk right into them. - Audit service registrations for newly added DLLs mimicking system component names.
Key takeaways
- Go’s
pclntabis your friend. Run GoReSym first, always. The runtime metadata that frustrates AV hands you a symbol table, and GoSerpent’s real logic is a few hundred bytes of relabelledmain.*functions inside a mountain of scheduler noise. - The secrets are in the launch argument, not the binary. AES-CBC (fixed IV
31323334...) protects the C2 and password; ChaCha20 keyed on SHA-256 of that password protects the channel. Break at the decrypt call and read them from memory. - Detect the staging, not the exfil. GoSerpent moves data over authenticated SMB, so
thumbcache_605a.db, theC:\Users\Public\Libraries\*.dbconfig, and canary files matter far more than outbound-upload alerts. - TmcLoader hides its APIs with circular XOR plus Base64 and injects fileless into
svchost.exe. Static import scans miss it. Find and run the decode stub. - Attribution is a hypothesis. TetrisPhantom overlap is Kaspersky’s working call, not a verdict. The stronger, safer claim is the pattern: Go, blended cloud/legit-service C2, and multi-month patient dwell now define this tier of espionage tooling, GoSerpent, BRICKSTORM, and GopherWhisper alike.
- Pull the exact hashes and 11 C2 IPs from the primary Securelist report before you operationalise any of this. The behavioural indicators here are durable; the network IOCs rotate.
References
- GoReSym
- GoSerpent backdoor attacks in Southeast Asia | Securelist (Kaspersky GReAT)
- New GoSerpent Malware Targets Southeast Asian Governments and Diplomats for Espionage | The Hacker News
- MITRE ATT&CK – Remote Access Software, Technique T1219 (Enterprise)
- MITRE ATT&CK – OS Credential Dumping, Technique T1003 (Enterprise)
- MITRE ATT&CK – Exfiltration Over C2 Channel, Technique T1041 (Enterprise)
- MITRE ATT&CK – Develop Capabilities: Malware, Sub-technique T1587.001 (Enterprise)