typetechniqueconfidencehighcreated2026-05-31updated2026-06-05obfuscationgolanganti-analysisevasion

Fused String API Decoding

A Go anti-analysis technique where Windows DLL names and their exported API names are concatenated into single large strings in .rdata, then sliced at runtime into individual syscall.LoadLibrary / GetProcAddress targets. Nets the attacker two things: strings tools only see the monolithic blob, and naive YARA/Sigma rules looking for VirtualAlloc in isolation miss the binary entirely.

Detection / Fingerprint

  • Look for .rdata strings that fuse kernel32.dll with one or more API names with no delimiter: kernel32.dllVirtualAllocGetTempPathWinvalid_slothost_is_down... ^[strings.txt:1615]
  • Look for Go binaries whose IAT imports only kernel32.dll yet runtime resolves dozens of APIs via syscall.LoadLibrary ^[pefile.txt:292-347]
  • Function names are randomized Go main-package symbols (sym.main.rxzsirwk, sym.main.lkezaikuw, sym.main.xhqsnoekoztspb) ^[r2:sym.main.rxzsirwk]

Implementation Patterns Observed

In lummastealer (e03dd36f), the implementation pattern is:

  1. Table construction (sym.main.rxzsirwk) — allocates a slice of Go string objects via runtime.newobject. Each object points to a substring inside a fused .rdata blob with a hardcoded length prefix. ^[r2:sym.main.rxzsirwk @ 0x140089ce0]
  2. Index-based loading (sym.main.lkezaikuw) — receives two integer indices (start byte, length), bounds-checks them against the fused blob, calls sym.main.xhqsnoekoztspb to produce a Go string, then passes it to syscall.LoadLibrary. ^[r2:sym.main.lkezaikuw @ 0x140087e80]
  3. String helper (sym.main.xhqsnoekoztspb) — pure runtime.slicebytetostring wrapper. ^[r2:sym.main.xhqsnoekoztspb @ 0x140088620]

Reproduce on Your Own VMs

package main

import (
	"fmt"
	"syscall"
	"unsafe"
)

// fusedBlob contains: kernel32.dllVirtualAllocGetTempPathW...
var fusedBlob = []byte(
	"kernel32.dllVirtualAllocGetTempPathWshell32.dllShellExecuteW")

func loadFromFused(offset, length int) uintptr {
	sub := string(fusedBlob[offset : offset+length])
	mod, _ := syscall.LoadLibrary(sub)
	return uintptr(mod)
}

func main() {
	// "VirtualAlloc" starts at byte 12, length 12
	v := loadFromFused(12, 12)
	fmt.Printf("resolved handle: %x\n", v)
}

Build with:

GOOS=windows GOARCH=amd64 go build -ldflags="-s -w -trimpath" fused.go

Then run strings against the binary — VirtualAlloc should not appear as a standalone string.

Defensive Countermeasures

  • Memory-based string extraction after the table is built (e.g., floss dynamic) may still recover sliced strings.
  • Behavioral detection: single-import kernel32.dll Go binary that rapidly loads advapi32.dll, ws2_32.dll, crypt32.dll, etc. via LoadLibrary is suspicious.
  • Sigma rule: monitor for LoadLibrary calls from a parent process with no corresponding import table entries.

Pages Where Observed

  • lummastealere03dd36f sibling
  • 9d2ca329149758 sibling: main.Xjnzvbmn constructs a buffer from a fused .rdata blob (VirtualAllocrandautoseedsweepWaiters...), slices it via main.uciyqnpcdd / main.qhybyctuf, and passes the result to main.sncqxmmgztyymei which resolves the API through syscall._LazyProc_.Call. ^[/intel/analyses/2914975816372d0dc79b777915f66955d312213ea036b84ff16ad5ab0bcfdd66.html]