| name | reduce-edr-detections |
| description | Use when reducing VirusTotal/EDR detection rates on compiled binaries — A/B testing + PE structural analysis for ML classifier signals, plus a bisection methodology for locating named YARA-style signatures |
| allowed-tools | Read, Bash, Grep, Glob, Write, Edit, Agent, AskUserQuestion |
Reduce EDR Detections
Systematic methodology for reducing VirusTotal and EDR detection rates on compiled binaries through comprehensive structural analysis, iterative A/B testing, and ML feature vector optimization.
When to Use
- VT detection rate is too high on compiled binaries
- ML classifiers (Wacatac, MalwareX-gen, ML.Attribute, Evo-gen, etc.) are flagging output
- Need to identify which binary attributes trigger detection
- After changes that may alter the PE/ELF structure
- Preparing binaries for deployment
Prerequisites
- VT API key at
/path/to/vt_apikey
pefile and lief Python libraries — preferably in a venv (python3 -m venv .venv && . .venv/bin/activate && pip install pefile lief). On PEP 668 systems where you can't use a venv, add --break-system-packages to a system-wide pip install.
- PE Structural Analyzer script: see
references/pe-structural-analyzer.md
- A vanilla binary from the same toolchain as your target (e.g.,
GOOS=windows GOARCH=amd64 go build)
Core Principles
- Triage detection type before choosing remediation. A YARA-style verdict (
Trojan/Win.Sliver.R774471) and an ML verdict (Wacatac.B!ml, ML.Attribute.HighConfidence) require fundamentally different fixes. YARA matches fixed bytes — rename strings, swap imports, restructure sections. ML is a statistical classifier — renaming strings cannot defeat it, and trying often makes things worse. Label every hit before starting.
- Change ONE variable per experiment. 10–20 samples per test. Build control and variant in the same VT upload window. ML models (especially Microsoft Wacatac.B!ml) retrain on approximately a daily cadence. A control batch built today and a variant built tomorrow is not a valid A/B test — half the observed delta will be model drift.
- Don't fight the toolchain identity. Making a Go binary look less like Go creates inconsistencies that increase detection — including renaming natural internal type names (e.g.
Allocator, Preamble) that show up slightly over-represented in detected samples. That's likely strings -n 6 extraction noise, not a real signal. See references/experiment-categories.md.
- Validate
strings -n 6 tokens against source before acting on them. The strings tool glues adjacent in-memory strings together, producing tokens that look like meaningful symbols but are two unrelated strings concatenated across a buffer boundary. Grep the actual source tree to confirm before making a suspicious token a hypothesis.
- Camouflage, not concealment. The goal is to give the classifier a believable answer to "what is this binary?" Mimicking the gopclntab symbol fingerprint of a single large real Go project (ghost profiling) consistently outperforms stripping, obfuscating, or padding. One coherent project; blending multiple produces a binary that matches no known software.
- VT is not ground truth, and there is an irreducible floor. Microsoft's cloud ML is substantially more aggressive than the local Defender engine. A binary at 100% Wacatac on VirusTotal can be clean on a real endpoint. Once detection drops to roughly 15–25% (the stochastic floor near the ML threshold), further structural optimization rarely pays back.
- Measure everything. Use the full PE structural analyzer before and after each change. Features you don't measure can't be correlated with detections.
- Compare against vanilla. Always compare your binary against a clean vanilla binary from the same toolchain. The delta between them is your ML signal.
- ML classifiers use feature vectors, not individual features. Compound anomalies accumulate — fix the ones that diverge most from the vanilla baseline.
- On-sensor EDR models are purely additive. Reverse engineering of a major EDR's on-sensor ML model revealed 20 gradient-boosted trees with 1,000 binary features, ALL leaf weights positive (0.05–2.25) — the model only penalizes, never rewards. Zero triggered features = score 0.0 = always passes. There is no "benign bonus" for looking legitimate — only penalties for looking malicious. Fewer anomalies = lower score = less detection.
On-Sensor EDR Model Intelligence
Reverse engineering of a major EDR vendor's kernel driver and on-sensor ML model produced verified intelligence about how the static analysis pipeline works. This informs which PE features to prioritize.
Model Architecture
- 20 gradient-boosted decision trees, 8,976 total binary-feature nodes
- 1,000-dimensional binary feature vector (present/absent per indicator)
- Purely additive scoring — all 514 non-trivial leaf weights are positive (0.05–2.25)
- Binary feature model — features are primarily binary (is indicator X set?), though ~380 float64 split values exist in the model data for computed features like entropy scores
- 2 sub-models (likely benign vs malicious binary classifiers)
EDR Static Analysis Passes (Verified)
The EDR kernel driver runs these passes on every file write / process creation:
| Pass | Codes | Feature range | What it checks |
|---|
| BM (Binary Metadata) | BM00–BM37 | 205–242 | PE headers, sections, data directories — presence/absence checks |
| Re (Recognition) | Re01–Re43 | 69–111 | Import table capability detection — 43 API categories |
| BR (Binary Recognition) | BR00–BR61 | 259–356 | Byte pattern signatures from signature update files (updated daily) |
| CR (Content Recognition) | CR00–CR18 | 357–374 | Multi-pattern content scanning (Aho-Corasick style) |
| cC (Content Category) | cC01–cC34 | 375–408 | Content type classification (native PE, .NET, script, packer) |
| Te (Text Analysis) | Te00–Te22 | 124–125+ | String content: URLs, IPs, file paths, encoded strings |
| FPE (Feature PE) | FPE0–FPE2 | 950–952 | PE entropy analysis, section anomalies, overlay data |
| Pes (PE Sections) | Pes1–Pes3 | 121–123 | High-entropy executable sections, non-standard names |
| Fs (Filesystem) | Fs01–FsHl | 66, 112–120 | File location, attributes — FsHl is rank #4 in model |
| SM (Static Model) | SM00–SM11 | 250–258 | ML model sub-scores (the model evaluating itself) |
Import Capability Detection — Ranked by Model Weight
The Re pass checks the import table only (IAT/ILT). APIs resolved dynamically via GetProcAddress or direct syscalls are invisible to this pass.
| Re code | Model rank | APIs detected | Go binary relevance |
|---|
| Re07 | #3 | CreateProcess, ShellExecute, WinExec | Go imports CreateProcess via kernel32 |
| Re27 | #6 | GetThreadContext, SetThreadContext | Not in standard Go |
| Re40 | #7 | CreateMutex, OpenMutex | Go's sync package may import |
| ReUM | #15 | High import table diversity score | Go binaries have many imports |
| Re01 | #16 | CreateToolhelp32Snapshot, EnumProcesses | Depends on Go code |
| Re03 | #17 | SetWindowsHookEx, GetAsyncKeyState | Not in standard Go |
| Re17 | #19 | VirtualAllocEx | Not in standard Go |
| Re18 | #20 | CreateRemoteThread | Not in standard Go |
Key for Go binaries: Re07 (CreateProcess) and ReUM (import breadth) are the highest-impact controllable features. Go's runtime imports many DLLs by default — each additional enriched DLL contributes to the ReUM score. Pruning unused DLL imports is high-leverage.
BM Pass — What Triggers PE Metadata Indicators
BM indicators fire based on presence/absence of PE header fields (zero/non-zero checks):
| BM code | Fires when | Go binary impact |
|---|
| BM12 | Exactly 1 PE section | Not applicable — Go has 16+ sections |
| BM34 | Exactly 1 import DLL | Not applicable — Go imports multiple |
| BM11 | Debug directory present | Go vanilla has none — adding one is inconsistent |
| BM16 | Non-standard section names (4 checks) | Go's numeric sections (/4, /19) trigger this |
| BM22 | Certificate/Security directory present | Signing adds this — positive for Go builds |
| BM07–BM10 | Data directory entries (Import, Export, Resource, Exception) present | Standard Go has Import only |
Key insight: BM12 and BM34 flag minimal PEs. Go binaries are naturally safe here. BM16 fires on Go's numeric section names — this is expected for the toolchain and not worth fighting (Principle 3).
FPE Pass — Entropy Thresholds
| Code | Meaning | Threshold (inferred from macOS equivalent) |
|---|
| FPE0 | High-entropy code section | Likely ~7.0 for .text (Go vanilla: 6.12, modified: 6.99) |
| FPE1 | Section attribute anomalies (W+X) | Any section with both write AND execute flags |
| FPE2 | Overlay/appended data | Data after last PE section |
Key for Go binaries: A Go binary with embedded WASM/compressed data in debug-like sections pushes file entropy from 6.85 to 7.44. If .text entropy reaches 7.0+, FPE0 fires. XOR padding .text to 6.99 is right at the threshold — avoid.
Cloud Prediction (Second Tier)
The EDR also sends the first 10,000 bytes of the PE to a cloud ML model for a second opinion. This means:
- PE headers + first section content are deeply analyzed in the cloud
- Content beyond 10KB is invisible to the cloud tier
- Results are cached (LRU, 10 entries) — first scan matters most
- Cloud model can be more aggressive than on-sensor model
Implication for Go binaries: Go PE headers are in the first 10KB. Ensure header fields are maximally consistent with vanilla Go in this region.
Note: The pass names (BM, Re, BR, CR, cC, Te, FPE, Pes, Fs, SM) are internal indicator code prefixes extracted from the EDR kernel driver. They are referenced in the analyzer output and experiment tracking.
What Static Analysis Cannot See (Verified Blind Spots)
- Dynamic API resolution — GetProcAddress calls are invisible to the import analysis pass
- Section content beyond byte patterns — If your content doesn't match a pattern category, it's opaque
- Runtime behavior from static scan — The static model cannot predict what code will DO
- Files > 10KB in the cloud — Only the first 10KB goes to the cloud model
WARNING: Static Evasion Is Not Sufficient
Passing the static ML model only gets the binary to START executing. The EDR kernel driver also registers runtime callbacks that catch operations regardless of how they're invoked:
| Callback | Monitors | Evaded by syscalls? |
|---|
| PsSetCreateProcessNotifyRoutineEx | Every process creation | No |
| PsSetCreateThreadNotifyRoutine | Every thread creation | No |
| PsSetLoadImageNotifyRoutine | Every DLL/EXE load | No |
| ObRegisterCallbacks | Handle operations (OpenProcess) | No |
| CmRegisterCallbackEx | All registry operations | No |
| FltRegisterFilter | All file I/O | No |
In-memory pattern scanning (a user-mode servlet) also scans process memory after execution, catching decrypted payloads, reflective DLLs, and C2 beacons.
AMSI intercepts PowerShell, VBScript, JScript, and .NET content at the kernel level.
These checks happen AT RUNTIME — after the static model has already passed or failed the binary. The static model optimization in this skill addresses the FIRST gate only. Runtime behavioral correlation is a separate detection layer that this skill does not address.
Alternative Execution Models (Bypass Static Analysis Entirely)
Non-PE execution models bypass the PE-centric static analysis pipeline completely:
| Model | Static model | AMSI | Runtime callbacks |
|---|
| Python/Lua/Ruby scripts | Bypassed | Not covered | Actions still visible |
| WASM runtime (wasmtime) | Bypassed | Bypassed | Actions still visible |
| .NET CLR in-process hosting | Bypassed | Likely bypassed | Actions still visible |
| Raw shellcode (VirtualAlloc) | Bypassed | Bypassed | Actions still visible |
If detection rates cannot be reduced sufficiently through PE optimization, consider whether the payload can be restructured as a non-PE execution model where the static analysis pipeline has no PE to analyze.
Phase 1: Establish Baseline
Build 10–20 identical-purpose samples. Upload all to VT and record per-engine results.
VT_KEY=$(cat /path/to/vt_apikey)
UPLOAD_URL=$(curl -s 'https://www.virustotal.com/api/v3/files/upload_url' \
-H "x-apikey: $VT_KEY" | python3 -c "import sys,json; print(json.load(sys.stdin)['data'])")
for f in /tmp/samples/*.exe; do
curl -s --request POST --url "$UPLOAD_URL" \
--header "x-apikey: $VT_KEY" --form "file=@$f" > /dev/null
sleep 16
done
sleep 300
sha256() { command -v sha256sum >/dev/null && sha256sum "$1" | cut -d' ' -f1 || shasum -a 256 "$1" | cut -d' ' -f1; }
for f in /tmp/samples/*.exe; do
sha=$(sha256 "$f")
curl -s "https://www.virustotal.com/api/v3/files/$sha" -H "x-apikey: $VT_KEY" | \
python3 -c "import sys,json; d=json.load(sys.stdin); r=d['data']['attributes']['last_analysis_results']; dets={k:v for k,v in r.items() if v['category']=='malicious'}; print(f'{len(dets)}: {list(dets.keys())}')"
sleep 4
done
Create a detection label file for the analyzer:
{ "sample-01.exe": "clean", "sample-02.exe": "detected", ... }
Phase 2: Comprehensive Structural Analysis
Use the full PE Structural Analyzer (see references/pe-structural-analyzer.md). It extracts 13 ML-relevant feature categories in one pass:
python3 pe_structural_analyzer.py /tmp/samples/ \
--baseline /tmp/vanilla_go.exe \
--detections /tmp/detections.json
Outputs: /tmp/pe_analysis.json (full), /tmp/pe_analysis.csv (flat), console clean-vs-detected comparison.
Build the Vanilla Baseline
mkdir -p /tmp/vanilla && cd /tmp/vanilla && go mod init hello
printf 'package main\nimport ("fmt";"os")\nfunc main(){fmt.Println("hello");os.Exit(0)}' > main.go
GOOS=windows GOARCH=amd64 go build -o /tmp/vanilla_go.exe .
Read the Baseline Delta
The analyzer prints every feature where your binary diverges from the vanilla baseline. Focus on:
- Features ADDED by your modifications (phantom sections, extra DLLs, resources)
- Features AMPLIFIED (BSS ratio from 6x to 208x, debug sections from 27% to 50%)
- Features that CONTRADICT the toolchain identity (wrong stack reserve for Go, linker version mismatch)
For the full taxonomy of what ML classifiers measure per feature category, see references/pe-structural-features.md.
Clean vs. Detected Statistical Comparison
The analyzer outputs Cohen's d between clean and detected groups automatically. Guidance:
| Effect Size | Action |
|---|
| d > 0.8 | Investigate immediately |
| d > 0.5 | Worth testing |
| d > 0.3 | Low priority |
| d < 0.3 | Noise — skip |
With fewer than 5 clean samples, Cohen's d is unreliable. When all samples are structurally identical and detection is stochastic, you need to shift the entire feature vector, not individual features.
Phase 3: Hypothesis Testing
For each signal from Phase 2:
- Form hypothesis: "Removing X will move feature Y toward vanilla baseline"
- One change only
- Build 10–20 samples
- Run the analyzer — verify the feature actually shifted before uploading
- Upload to VT, wait, pull results
- Compare: clean rate, anomaly score, vanilla delta
- Decision: improved → keep, same/worse → revert
See references/experiment-categories.md for the safe vs. dangerous change taxonomy, and the experiment tracking table template.
Testing Anti-patterns
- Don't overindex on small samples — n<5 correlations are noise
- Don't change multiple variables simultaneously
- Don't assume causation from correlation
- Don't fight the toolchain — a Go binary should look like Go, not MSVC
Phase 4: Iterate
Repeat Phases 2–3 until: clean rate plateaus, remaining detections show no structural pattern, or feature vector matches vanilla baseline as closely as possible.
Phase 5: Validate at Scale
Final validation with 20–30 samples of the real payload. Record as the release benchmark.
Localizing a Persistent Named Signature (Bisection Method)
Use this when one specific named vendor signature keeps firing on 100% of your samples after Phases 1–4 have shifted the ML/heuristic cluster. Vendor-specific named labels (e.g., Trojan/Win.Sliver.R774471, WinGo/SomeFamily.A) that are stable across distinct file hashes are YARA-style byte/string rules — Phase 3 statistical changes don't move them and never will. You have to find the literal feature the rule keys on and remove it at the source.
This is the YARA-fix track of Core Principle 1 (Triage detection type before choosing remediation). The Phases 1–5 flow handles the ML side; this section handles the YARA side.
When to use
- A single named signature is at 100/100% across your population while other engines fluctuate
- The label is reproducible across distinct SHA256s and per-build polymorphism
- Phase 3 hypotheses (entropy, sections, import pruning, signing diversification) don't move it
- You'd rather characterize the rule once than chase it through five more experiments
Procedure
- Pick a clean-signal sample. Prefer one whose only detection is the named signature, or that signature plus 1–2 generic engines. Less noise means less ambiguity. Note the SHA256.
- Tail-zero sweep. Generate variants where progressively larger tail fractions of the file are zeroed (1/16, 1/8, 1/4, 1/2, 3/4, 7/8). Upload all to VT. The smallest cut where the named signature stops firing bounds the signal region from below; the largest cut where it still fires bounds it from above.
- Bisect inside the region. Split the bounded region into 4 quarters and zero each independently. Whichever quarter(s) make the signature drop is where the signal lives. Repeat — split that quarter into halves, then thirds — until you're at sub-100KB resolution.
- Catalog the localized content. Extract ASCII strings (
strings -n 12) and byte patterns from the surviving region. Look for tokens that are:
- Tool-specific (package paths from your project / its dependencies, distinctive function names, embedded resource markers) vs generic Go/C runtime
- Stable across builds (not affected by your per-build polymorphism)
- High cardinality (a string with thousands of instances is a better YARA target than a string with three)
- String-family substitution. For each candidate family, globally replace every occurrence in the file with same-length junk and upload. The family whose elimination flips the signature is the YARA hook. (Note: families can be additive — sometimes only the union of two-or-three families flips it.)
- Structural variant probe. Once you have a candidate string, replace its prefix only, then middle only, then suffix only — each with same-length filler bytes — to identify which parts are load-bearing. This distinguishes:
- Literal substring matches (
/core must be exactly there)
- Regex/shape matches (
internal/<word>/<word>/internal/ — any words work, but the structure is fixed)
- Token-set matches (
engine, compiler, objx all need to be present somewhere)
- Apply the fix at the source and verify on fresh builds. Post-hoc byte substitution can miss interactions with per-build polymorphism. Once you've identified the YARA hook, change the code that generates the offending string, then build a fresh N=20 batch and confirm the named signature stays at 0/20.
Operational notes
- Same-length substitutions only. Length-altering edits shift PE offsets and break the section table. Use zero-padding or equal-length junk strings; never insert or delete bytes.
- Signing is not a blocker. Byte-level edits invalidate the Authenticode signature, but AV scanners scan the file regardless of cert validity. Don't waste time re-signing intermediate variants.
- VT API budget. Free tier ≈ 4 req/min, 250/day. A complete bisection from a 16MB file down to a 256KB localized region typically takes 5 rounds × 4–7 uploads = 25–40 uploads, ~30–45 minutes elapsed (the per-round VT analysis wait dominates).
- One named signature at a time. If multiple engines fire, focus on the most vendor-specific named one (e.g., ESET family labels > generic
MalwareX-gen). Generic heuristic labels often disappear on their own when the named signature is killed because they were shadowing the same content.
- Watch for shadowed signals. After you kill the named signature, a previously-invisible engine may jump from 0% to 80%. That's not a regression — it's a separate rule that was being masked. Treat it as the next target.
Worked example
ESET WinGo/WasmForge.A (publicly documented in wasmforge ≥v1.0.4):
- Tail-zero placed the signal in the .rdata section (file bytes ~4.2MB–8.4MB)
- Quarter-bisection of .rdata showed signal across multiple quarters — initial hypothesis was a count/score rule
- String-family substitution isolated
internal/azure/core (2,997 instances) as load-bearing; replacing it with same-length junk flipped the verdict to clean. Other dense families (engine/compiler ×1,517, /internal/engine ×1,714, objx ×554) did not flip the verdict when removed individually
- Structural variants pinned the rule to the
internal/ PREFIX, NOT the /core suffix, NOT the middle word. xxxxxxxx/azure/core → CLEAN; internal/azure/yyyy → still HIT; internal/zzzzz/core → still HIT
- Root cause: the hardcoded
internal/<word>/core format for the renamed wazero fork's module path concatenated with wazero's own /internal/engine/... subdirectory tree to produce a doubled internal/.../internal/... shape in every type-name string baked into reflection metadata. The doubled internal/ is unusual in legitimate Go binaries.
- Fix: per-build random pick from a 13-shape pool, none of which start with
internal/. Result: 25/25 baseline → 0/20 ESET hits across the next 20-sample batch, with no other engine compensating
The total investigation budget was ~40 VT uploads, ~3 hours elapsed (most of it waiting on VT analysis between rounds).
Tracking Results
| Experiment | Change | Samples | Clean% | Anomaly Score | Vanilla Delta | Keep? |
|-----------|-----------------|---------|--------|---------------|---------------|-------|
| Baseline | — | 20 | 15% | 12 | 12 anomalies | — |
| Exp 1 | Remove .edata | 20 | 20% | 11 | 11 anomalies | Yes |
| Exp 2 | Patch linker v | 20 | 5% | 11 (incons.) | 13 anomalies | No |
Known Ineffective / Counterproductive Approaches
Proven Counterproductive (WORSE detection)
- Patching linker version (Go 3.0 → MSVC 14.x) — toolchain inconsistency
- Adding fake Rich header to Go binary — inconsistent with Go's section layout
- XOR padding .text section — raises entropy to 6.99, triggers Bkav/Trapmine
- Block-shuffle padding .text — ML detects .text SIZE anomaly
- Stripping .symtab — Go always has it; absence is inconsistent with toolchain
Proven Neutral (no effect)
- Code-level polymorphism (struct reorder, opaque predicates, AST transforms)
- Per-build filename randomization
Proven Effective
- DLL pool pruning (remove netapi32, ole32, winhttp — unused import heuristic). EDR verified: each extra DLL contributes to ReUM (rank #15 in model).
- Product name pruning (remove enterprise-sounding names that appear in malware datasets)
- Signing identity diversification — per-build random pick from a pool of plausible company names, description strings, and issuer suffixes eliminates stable YARA targets in the Authenticode blob. EDR verified: BM22 fires when certificate directory is present (positive — signing is good).
- Capping enriched DLLs at ≤3. EDR verified: ReUM measures import table diversity; fewer DLLs = lower ReUM contribution.
- YARA signature kill (per-build renaming of wazero/WASM package paths and exported names). EDR verified: BR pass uses 98 byte-pattern categories from daily-updated signature update files.
- Ghost profiling — replacing a binary's gopclntab with harvested function/package names from a large real Go project. The classifier sees "legitimate infrastructure software" instead of a minimal Go runtime. Larger, coherent single-project profiles produce reliably lower detection rates; blending multiple projects typically makes detection worse.
- Keeping .text entropy below 7.0 — EDR's FPE0 indicator fires on high-entropy code sections. Go vanilla .text is ~6.12; XOR padding pushes to 6.99 (borderline). Stay below 7.0.
- File placement matters — EDR's FsHl (rank #4 in model) heavily penalizes files in suspicious locations (%TEMP%, Downloads, %APPDATA%). Deploy to standard program directories.
- Bisection for stuck named signatures — when a single vendor-specific YARA-style label sits at 100% across all samples and Phase 3 changes don't move it, localize the rule's trigger bytes by progressive byte-zeroing + same-length string substitution (see Localizing a Persistent Named Signature). Verified: identified the structural pattern behind ESET
WinGo/WasmForge.A (a doubled internal/.../internal/ shape from concatenating the module-path rename prefix with wazero's own internal subtree) — fix dropped 25/25 hits to 0/20.
EDR Model Insights (Verified by Reverse Engineering)
- The model is purely additive — zero features triggered = score 0.0 = always passes. There is no "benign bonus." Every structural anomaly removed lowers the score.
- Leaf weights are bimodal: 0.05 or 2.0+. Non-trivial leaves are either "confident bad" (weight ~2.0–2.25, 92%) or "not sure" (weight ~0.05, 8%). Untriggered features produce zero-weight contributions. There are NO moderate signals.
- Go vanilla is at the detection boundary. A vanilla Go binary triggers 9 Re codes, causing multiple trees to reach strong leaves. Adding payload-specific indicators pushes the score higher. Each additional triggered indicator category moves the score further into detection range. The relationship is approximate — trees evaluate feature combinations, not single indicators.
- Import pruning has outsized impact. An implant adding VirtualAllocEx + CreateRemoteThread + OpenProcess (Re17+Re18+Re26) contributes significant additional score. Resolving these dynamically (GetProcAddress) or via syscalls removes their contribution.
- Import table is the #1 controllable attack surface — Re07 (CreateProcess, rank #3), Re40 (CreateMutex, rank #7), ReUM (breadth, rank #15) are the most impactful features you can control.
- Sensor context features dominate (ranks #1, #2) — CSpc and SCtb are internal sensor state you cannot control. FsHl (rank #4) is partially controllable — it penalizes files in suspicious locations (%TEMP%, Downloads, %APPDATA%). Deploy to standard program directories to avoid triggering it.
- The cloud model sees only the first 10KB of the PE. Structural headers are in this window — ensure they are maximally vanilla. Payload content beyond 10KB is invisible to the cloud tier.
- BM12 flags single-section PEs — Go binaries are safe here (16+ sections). BM34 flags single-import-DLL PEs — vanilla Go may trigger this (imports kernel32 only). Enriched builds with additional DLLs avoid BM34, but adding too many triggers ReUM. The balance: 2–3 DLLs.
- Signature patterns (BR pass) update daily. Byte-pattern evasion is transient. Structural evasion (reducing the feature vector toward vanilla baseline) is durable.
Go Runtime Unavoidable Baseline
A vanilla Go binary triggers these Re codes regardless of what your code does:
| Re | Importance | Go runtime API | Removable? |
|---|
| Re27 | 25 | GetThreadContext/SetThreadContext (goroutines) | No |
| Re01 | 22 | CreateToolhelp32Snapshot (runtime) | No |
| ReTe | 21 | GetStdHandle (console I/O) | No |
| Re05 | 0 | OpenProcessToken | Maybe — if not needed |
| Re19 | 0 | LoadLibraryExW | No |
| Re25 | 0 | TerminateProcess | No |
| Re28 | 0 | SuspendThread/ResumeThread (goroutines) | No |
| Re38 | 0 | VirtualProtect | No |
| ReGq | 0 | GetQueuedCompletionStatusEx (IOCP) | No |
Total Go vanilla importance: 68 (9 codes, ~9 strong trees)
Each payload-specific import (Re17 VirtualAllocEx, Re18 CreateRemoteThread, Re26 OpenProcess, Re09 ReadProcessMemory) adds another strong tree. The optimization target: keep total triggered Re codes as close to the vanilla 9 as possible.
References
Integration
Called By
- Manual invocation when detection rates increase
- After ML model updates (re-test existing samples to detect regression)
- Before deploying new binary versions