Install
$ agentstack add skill-praetorian-inc-reduce-golang-detections-skill-reduce-golang-detections-skill ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
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 pefileandliefPython 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-packagesto a system-widepip 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 likelystrings -n 6extraction noise, not a real signal. Seereferences/experiment-categories.md. - Validate
strings -n 6tokens against source before acting on them. Thestringstool 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 # free-tier rate limit
done
sleep 300 # wait for analysis
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 //internal/` — any words work, but the structure is fixed)
- Token-set matches (
engine,compiler,objxall 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 uploa
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: praetorian-inc
- Source: praetorian-inc/reduce-golang-detections-skill
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.