mirror of
https://github.com/praetorian-inc/reduce-golang-detections-skill
synced 2026-06-21 14:05:11 +00:00
Add verified EDR model intelligence from kernel driver RE
Integrate findings from reverse engineering a major EDR's kernel driver and on-sensor ML model into the skill and analyzer. - SKILL.md: model architecture, import capability rankings, scoring math, runtime detection warnings, alternative execution model guidance - pe_structural_analyzer.py: Re01-Re43 import mapping, BM/FPE simulation, 10KB cloud boundary, estimated EDR score output - New reference: edr-model-intelligence.md - Updated references with verified feature weights
This commit is contained in:
@@ -34,6 +34,129 @@ allowed-tools: Read, Bash, Grep, Glob, Write, Edit, Agent, AskUserQuestion
|
|||||||
7. **Measure everything.** Use the full PE structural analyzer before and after each change. Features you don't measure can't be correlated with detections.
|
7. **Measure everything.** Use the full PE structural analyzer before and after each change. Features you don't measure can't be correlated with detections.
|
||||||
8. **Compare against vanilla.** Always compare your binary against a clean vanilla binary from the same toolchain. The delta between them is your ML signal.
|
8. **Compare against vanilla.** Always compare your binary against a clean vanilla binary from the same toolchain. The delta between them is your ML signal.
|
||||||
9. **ML classifiers use feature vectors, not individual features.** Compound anomalies accumulate — fix the ones that diverge most from the vanilla baseline.
|
9. **ML classifiers use feature vectors, not individual features.** Compound anomalies accumulate — fix the ones that diverge most from the vanilla baseline.
|
||||||
|
10. **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)
|
||||||
|
|
||||||
|
1. **Dynamic API resolution** — GetProcAddress calls are invisible to the import analysis pass
|
||||||
|
2. **Section content beyond byte patterns** — If your content doesn't match a pattern category, it's opaque
|
||||||
|
3. **Runtime behavior from static scan** — The static model cannot predict what code will DO
|
||||||
|
4. **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
|
## Phase 1: Establish Baseline
|
||||||
|
|
||||||
@@ -164,18 +287,53 @@ Final validation with 20–30 samples of the real payload. Record as the release
|
|||||||
|
|
||||||
### Proven Effective
|
### Proven Effective
|
||||||
|
|
||||||
- DLL pool pruning (remove netapi32, ole32, winhttp — unused import heuristic)
|
- 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)
|
- 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
|
- 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
|
- 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)
|
- 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.
|
- 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.
|
||||||
|
|
||||||
|
### 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
|
## References
|
||||||
|
|
||||||
- [pe-structural-analyzer.md](references/pe-structural-analyzer.md) — Analyzer installation, usage, output format
|
- [pe-structural-analyzer.md](references/pe-structural-analyzer.md) — Analyzer installation, usage, output format
|
||||||
- [pe-structural-features.md](references/pe-structural-features.md) — 13-category ML feature taxonomy
|
- [pe-structural-features.md](references/pe-structural-features.md) — 13-category ML feature taxonomy with verified EDR model weights
|
||||||
- [experiment-categories.md](references/experiment-categories.md) — Safe/dangerous change taxonomy, experiment tracking template
|
- [experiment-categories.md](references/experiment-categories.md) — Safe/dangerous change taxonomy, experiment tracking template
|
||||||
|
- [edr-model-intelligence.md](references/edr-model-intelligence.md) — Verified on-sensor EDR model architecture, feature vector layout, import capability mapping
|
||||||
|
|
||||||
## Integration
|
## Integration
|
||||||
|
|
||||||
|
|||||||
+172
-1
@@ -449,7 +449,157 @@ def analyze_pe(filepath, label=""):
|
|||||||
'imphash': pe.get_imphash(),
|
'imphash': pe.get_imphash(),
|
||||||
}
|
}
|
||||||
|
|
||||||
# ===== 13. ANOMALY SCORES (composite) =====
|
# ===== 13. EDR MODEL FEATURES (verified from kernel driver RE) =====
|
||||||
|
|
||||||
|
# Re (Recognition) — import capability categories
|
||||||
|
# Maps imported APIs to Re01-Re43 indicator codes
|
||||||
|
re_api_map = {
|
||||||
|
'Re01': ['createtoolhelp32snapshot', 'process32first', 'process32firstw', 'process32next', 'process32nextw', 'enumprocesses'],
|
||||||
|
'Re03': ['setwindowshookexa', 'setwindowshookexw', 'getasynckeystate', 'getkeystate'],
|
||||||
|
'Re05': ['adjusttokenprivileges', 'openprocesstoken'],
|
||||||
|
'Re07': ['createprocessa', 'createprocessw', 'shellexecutea', 'shellexecutew', 'winexec'],
|
||||||
|
'Re09': ['readprocessmemory', 'ntreadvirtualmemory'],
|
||||||
|
'Re10': ['writeprocessmemory', 'ntwritevirtualmemory'],
|
||||||
|
'Re11': ['regsetvalueexa', 'regsetvalueexw', 'regcreatekeyexa', 'regcreatekeyexw'],
|
||||||
|
'Re12': ['regqueryvalueexa', 'regqueryvalueexw', 'regenumkeyexa'],
|
||||||
|
'Re13': ['openscmanagera', 'openscmanagerw', 'createservicea', 'createservicew'],
|
||||||
|
'Re14': ['openclipboard', 'getclipboarddata', 'setclipboarddata'],
|
||||||
|
'Re17': ['virtualallocex', 'ntallocatevirtualmemory'],
|
||||||
|
'Re18': ['createremotethread', 'createremotethreadex', 'rtlcreateuserthread', 'ntcreatethreadex'],
|
||||||
|
'Re19': ['loadlibrarya', 'loadlibraryw', 'loadlibraryexa', 'loadlibraryexw', 'ldrloaddll'],
|
||||||
|
'Re25': ['terminateprocess', 'ntterminateprocess'],
|
||||||
|
'Re26': ['openprocess', 'ntopenprocess'],
|
||||||
|
'Re27': ['getthreadcontext', 'setthreadcontext', 'ntgetcontextthread', 'ntsetcontextthread'],
|
||||||
|
'Re28': ['suspendthread', 'resumethread', 'ntsuspendthread'],
|
||||||
|
'Re29': ['urldownloadtofilea', 'urldownloadtofilew', 'winhttpconnect'],
|
||||||
|
'Re30': ['socket', 'connect', 'send', 'recv', 'wsastartup'],
|
||||||
|
'Re32': ['cryptacquirecontexta', 'cryptencrypt', 'cryptdecrypt', 'bcryptencrypt'],
|
||||||
|
'Re34': ['createnamedpipea', 'createnamedpipew', 'connectnamedpipe'],
|
||||||
|
'Re36': ['winhttpopen', 'httpsendrequesta', 'internetopena', 'httpopenrequesta'],
|
||||||
|
'Re37': ['mapviewoffile', 'mapviewoffileex', 'ntmapviewofsection'],
|
||||||
|
'Re38': ['virtualprotect', 'virtualprotectex', 'ntprotectvirtualmemory'],
|
||||||
|
'Re39': ['deviceiocontrol', 'ntdeviceiocontrolfile'],
|
||||||
|
'Re40': ['createmutexa', 'createmutexw', 'openmutexa'],
|
||||||
|
'Re42': ['bitblt', 'getdc', 'printwindow', 'getwindowdc'],
|
||||||
|
'Re43': ['debugactiveprocess', 'waitfordebugevent', 'debugbreakprocess'],
|
||||||
|
}
|
||||||
|
|
||||||
|
re_importance = {
|
||||||
|
'Re07': 27, 'Re27': 25, 'Re40': 25, 'ReUM': 22, 'Re01': 22, 'Re03': 22,
|
||||||
|
'Re17': 21, 'Re18': 21, 'Re26': 21, 'Re09': 21, 'Re13': 21,
|
||||||
|
'Re42': 21, 'ReTe': 21, 'Re10': 0, 'Re11': 0, 'Re12': 0,
|
||||||
|
'Re05': 0, 'Re14': 0, 'Re19': 0, 'Re25': 0, 'Re28': 0,
|
||||||
|
'Re29': 0, 'Re30': 0, 'Re32': 0, 'Re34': 0, 'Re36': 0,
|
||||||
|
'Re37': 0, 'Re38': 0, 'Re39': 0, 'Re43': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
all_imports_lower = set()
|
||||||
|
if hasattr(pe, 'DIRECTORY_ENTRY_IMPORT'):
|
||||||
|
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||||
|
for imp in entry.imports:
|
||||||
|
if imp.name:
|
||||||
|
all_imports_lower.add(imp.name.decode().lower())
|
||||||
|
|
||||||
|
re_triggered = {}
|
||||||
|
for re_code, apis in re_api_map.items():
|
||||||
|
matching = [a for a in apis if a in all_imports_lower]
|
||||||
|
if matching:
|
||||||
|
re_triggered[re_code] = matching
|
||||||
|
|
||||||
|
re_um_triggered = len(import_dlls) > 4 or total_imports > 80
|
||||||
|
if re_um_triggered:
|
||||||
|
re_triggered['ReUM'] = [f'{len(import_dlls)} DLLs, {total_imports} imports']
|
||||||
|
|
||||||
|
features['edr_re_codes'] = {
|
||||||
|
'triggered': re_triggered,
|
||||||
|
'triggered_count': len(re_triggered),
|
||||||
|
'triggered_codes': sorted(re_triggered.keys()),
|
||||||
|
'total_importance': sum(re_importance.get(c, 0) for c in re_triggered),
|
||||||
|
'high_importance_triggered': sorted(
|
||||||
|
[(c, re_importance.get(c, 0)) for c in re_triggered if re_importance.get(c, 0) >= 21],
|
||||||
|
key=lambda x: -x[1]
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
# BM (Binary Metadata) — PE header presence/absence checks
|
||||||
|
bm_triggered = []
|
||||||
|
if fh.NumberOfSections == 1:
|
||||||
|
bm_triggered.append('BM12_single_section')
|
||||||
|
if len(import_dlls) == 1:
|
||||||
|
bm_triggered.append('BM34_single_import_dll')
|
||||||
|
if data_dirs.get('DEBUG', {}).get('size', 0) > 0:
|
||||||
|
bm_triggered.append('BM11_debug_dir_present')
|
||||||
|
if data_dirs.get('SECURITY', {}).get('size', 0) > 0:
|
||||||
|
bm_triggered.append('BM22_certificate_present')
|
||||||
|
if data_dirs.get('CLR_RUNTIME', {}).get('size', 0) > 0:
|
||||||
|
bm_triggered.append('BM30_clr_header')
|
||||||
|
if data_dirs.get('DELAY_IMPORT', {}).get('size', 0) > 0:
|
||||||
|
bm_triggered.append('BM33_delay_import')
|
||||||
|
if data_dirs.get('IAT', {}).get('size', 0) > 0:
|
||||||
|
bm_triggered.append('BM35_iat_present')
|
||||||
|
|
||||||
|
# Check for non-standard section names (BM16)
|
||||||
|
standard_names = {'.text', '.data', '.rdata', '.rsrc', '.reloc', '.pdata',
|
||||||
|
'.bss', '.edata', '.idata', '.tls', '.debug', 'INIT', 'PAGE'}
|
||||||
|
nonstandard = [s['name'] for s in features.get('sections', {}).get('details', [])
|
||||||
|
if s.get('name', '') not in standard_names
|
||||||
|
and not s.get('name', '').startswith('/')]
|
||||||
|
if nonstandard:
|
||||||
|
bm_triggered.append(f'BM16_nonstandard_names({len(nonstandard)})')
|
||||||
|
|
||||||
|
features['edr_bm_codes'] = {
|
||||||
|
'triggered': bm_triggered,
|
||||||
|
'triggered_count': len(bm_triggered),
|
||||||
|
}
|
||||||
|
|
||||||
|
# FPE (Feature PE) — entropy and structure
|
||||||
|
fpe_triggered = []
|
||||||
|
text_entropy = 0.0
|
||||||
|
for s in features.get('sections', {}).get('details', []):
|
||||||
|
if s.get('name') in ('.text', '.code'):
|
||||||
|
text_entropy = s.get('entropy', 0)
|
||||||
|
if text_entropy > 7.0:
|
||||||
|
fpe_triggered.append(f'FPE0_high_text_entropy({text_entropy:.2f})')
|
||||||
|
|
||||||
|
# W+X sections
|
||||||
|
for s in features.get('sections', {}).get('details', []):
|
||||||
|
if s.get('is_executable') and s.get('is_writable'):
|
||||||
|
fpe_triggered.append(f'FPE1_wx_section({s.get("name")})')
|
||||||
|
|
||||||
|
if features.get('overlay', {}).get('present'):
|
||||||
|
fpe_triggered.append('FPE2_overlay_present')
|
||||||
|
|
||||||
|
features['edr_fpe_codes'] = {
|
||||||
|
'triggered': fpe_triggered,
|
||||||
|
'triggered_count': len(fpe_triggered),
|
||||||
|
'text_entropy': round(text_entropy, 4),
|
||||||
|
}
|
||||||
|
|
||||||
|
# 10KB cloud boundary analysis
|
||||||
|
features['cloud_boundary'] = {
|
||||||
|
'first_10kb_size': min(total_size, 10000),
|
||||||
|
'content_beyond_10kb': max(0, total_size - 10000),
|
||||||
|
'pct_visible_to_cloud': round(min(10000, total_size) / total_size * 100, 1) if total_size > 0 else 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Estimated EDR model score (rough approximation)
|
||||||
|
total_indicators = len(re_triggered) + len(bm_triggered) + len(fpe_triggered)
|
||||||
|
features['edr_score_estimate'] = {
|
||||||
|
'total_indicators_triggered': total_indicators,
|
||||||
|
're_codes_triggered': len(re_triggered),
|
||||||
|
'bm_codes_triggered': len(bm_triggered),
|
||||||
|
'fpe_codes_triggered': len(fpe_triggered),
|
||||||
|
'estimated_strong_trees': min(20, total_indicators),
|
||||||
|
'estimated_score_range': f'{total_indicators * 0.5:.0f}–{min(45, total_indicators * 2.25):.0f}',
|
||||||
|
'verdict': (
|
||||||
|
'LIKELY CLEAN' if total_indicators <= 8
|
||||||
|
else 'BORDERLINE' if total_indicators <= 12
|
||||||
|
else 'LIKELY DETECTED' if total_indicators <= 16
|
||||||
|
else 'HIGH CONFIDENCE DETECTION'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== 14. ANOMALY SCORES (composite) =====
|
||||||
anomalies = 0
|
anomalies = 0
|
||||||
anomaly_details = []
|
anomaly_details = []
|
||||||
|
|
||||||
@@ -562,6 +712,27 @@ def flat_features_for_csv(features):
|
|||||||
flat['anomaly_score'] = features['anomaly_score']['total']
|
flat['anomaly_score'] = features['anomaly_score']['total']
|
||||||
flat['anomaly_details'] = '|'.join(features['anomaly_score']['details'])
|
flat['anomaly_details'] = '|'.join(features['anomaly_score']['details'])
|
||||||
|
|
||||||
|
# EDR model features
|
||||||
|
edr_re = features.get('edr_re_codes', {})
|
||||||
|
flat['edr_re_count'] = edr_re.get('triggered_count', 0)
|
||||||
|
flat['edr_re_codes'] = '|'.join(edr_re.get('triggered_codes', []))
|
||||||
|
flat['edr_re_importance'] = edr_re.get('total_importance', 0)
|
||||||
|
|
||||||
|
edr_bm = features.get('edr_bm_codes', {})
|
||||||
|
flat['edr_bm_count'] = edr_bm.get('triggered_count', 0)
|
||||||
|
flat['edr_bm_codes'] = '|'.join(edr_bm.get('triggered', []))
|
||||||
|
|
||||||
|
edr_fpe = features.get('edr_fpe_codes', {})
|
||||||
|
flat['edr_fpe_count'] = edr_fpe.get('triggered_count', 0)
|
||||||
|
flat['edr_fpe_text_entropy'] = edr_fpe.get('text_entropy', 0)
|
||||||
|
|
||||||
|
cloud = features.get('cloud_boundary', {})
|
||||||
|
flat['cloud_pct_visible'] = cloud.get('pct_visible_to_cloud', 0)
|
||||||
|
|
||||||
|
score = features.get('edr_score_estimate', {})
|
||||||
|
flat['edr_total_indicators'] = score.get('total_indicators_triggered', 0)
|
||||||
|
flat['edr_verdict'] = score.get('verdict', '')
|
||||||
|
|
||||||
return flat
|
return flat
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
# On-Sensor EDR Model Intelligence
|
||||||
|
|
||||||
|
Verified findings from reverse engineering a major EDR vendor's kernel driver and on-sensor
|
||||||
|
ML model. Source: kernel driver, model binary, user-mode service.
|
||||||
|
|
||||||
|
## On-Sensor ML Model
|
||||||
|
|
||||||
|
- 20 gradient-boosted trees, 8,976 binary-feature nodes
|
||||||
|
- 1,000-dimensional binary feature vector (present/absent)
|
||||||
|
- ~9,100 leaf weights, ALL positive (0.05–2.25) — purely additive
|
||||||
|
- 2 sub-models (benign/malicious)
|
||||||
|
- Model stored in .data section of a kernel-mode PE
|
||||||
|
|
||||||
|
## Feature Vector Layout
|
||||||
|
|
||||||
|
| Range | Source | Count | Purpose |
|
||||||
|
|-------|--------|------:|---------|
|
||||||
|
| 69–111 | Re01–Re43 | 43 | Import capability detection |
|
||||||
|
| 121–123 | Pes1–Pes3 | 3 | PE section indicators |
|
||||||
|
| 124–125 | Tes1–Tes2 | 2 | Text section features |
|
||||||
|
| 205–242 | BM00–BM37 | 38 | PE header metadata |
|
||||||
|
| 259–356 | BR00–BR61 | 98 | Byte pattern signatures |
|
||||||
|
| 357–374 | CR00–CR18 | 18 | Content recognition |
|
||||||
|
| 375–408 | cC01–cC34 | 34 | Content categories |
|
||||||
|
| 950–952 | FPE0–FPE2 | 3 | PE entropy/structure |
|
||||||
|
|
||||||
|
## Top 20 Model Features by Importance
|
||||||
|
|
||||||
|
| Rank | Feature | Code | Refs | Controllable? |
|
||||||
|
|-----:|--------:|------|-----:|:--------------|
|
||||||
|
| 1 | 165 | CSpc | 35 | No (sensor) |
|
||||||
|
| 2 | 167 | SCtb | 35 | No (sensor) |
|
||||||
|
| 3 | **75** | **Re07** | **27** | **Yes — import: CreateProcess** |
|
||||||
|
| 4 | 120 | FsHl | 26 | Partial — file location |
|
||||||
|
| 5 | 64 | AcBu | 25 | No |
|
||||||
|
| 6 | **95** | **Re27** | **25** | **Yes — import: GetThreadContext** |
|
||||||
|
| 7 | **108** | **Re40** | **25** | **Yes — import: CreateMutex** |
|
||||||
|
| 8 | 112 | Fs02 | 25 | No |
|
||||||
|
| 9–11 | 166–169 | SSsq/SCst/SCpi | 23 | No (sensor) |
|
||||||
|
| 12 | **124** | **Tes1** | **23** | **Yes — text section** |
|
||||||
|
| 15 | **68** | **ReUM** | **22** | **Yes — import diversity** |
|
||||||
|
| 16 | **69** | **Re01** | **22** | **Yes — import: process enum** |
|
||||||
|
| 17 | **71** | **Re03** | **22** | **Yes — import: user input** |
|
||||||
|
| 19 | **85** | **Re17** | **21** | **Yes — import: VirtualAllocEx** |
|
||||||
|
| 20 | **86** | **Re18** | **21** | **Yes — import: CreateRemoteThread** |
|
||||||
|
|
||||||
|
## Import Capability Categories (Re01–Re43)
|
||||||
|
|
||||||
|
The import analysis pass scans the IAT/ILT only. Dynamic resolution is invisible.
|
||||||
|
|
||||||
|
| Re | APIs | Go relevance |
|
||||||
|
|----|------|-------------|
|
||||||
|
| Re07 (rank #3) | CreateProcess, ShellExecute, WinExec | Go runtime imports CreateProcessW |
|
||||||
|
| Re40 (rank #7) | CreateMutex, OpenMutex | Go sync may import |
|
||||||
|
| ReUM (rank #15) | Import table diversity | Go has many DLLs by default |
|
||||||
|
| Re19 | LoadLibrary, LdrLoadDll | Go runtime uses LoadLibraryExW |
|
||||||
|
| Re25 | TerminateProcess | Go runtime imports this |
|
||||||
|
| Re38 | VirtualProtect | Go runtime uses this |
|
||||||
|
|
||||||
|
## BM Pass — Exact Conditions
|
||||||
|
|
||||||
|
| Code | Feature | Condition | Go impact |
|
||||||
|
|------|--------:|-----------|-----------|
|
||||||
|
| BM00 | 205 | PE signature valid | Always fires (good) |
|
||||||
|
| BM01 | 206 | NumberOfSections > 0 | Go has 16+ sections |
|
||||||
|
| BM12 | 217 | Exactly 1 section | Go safe — many sections |
|
||||||
|
| BM16 | 221 | Non-standard section names | Go numeric names (/4 /19) trigger this |
|
||||||
|
| BM22 | 227 | Certificate directory present | Signing sets this (positive) |
|
||||||
|
| BM34 | 239 | Exactly 1 import DLL | Go safe — multiple DLLs |
|
||||||
|
|
||||||
|
## FPE Pass — Entropy Analysis
|
||||||
|
|
||||||
|
| Code | Feature | Meaning | Threshold (inferred) |
|
||||||
|
|------|--------:|---------|---------------------|
|
||||||
|
| FPE0 | 950 | High-entropy code section | ~7.0 for .text |
|
||||||
|
| FPE1 | 951 | Section attribute anomalies (W+X) | Any write+execute |
|
||||||
|
| FPE2 | 952 | Overlay/appended data | Data past last section |
|
||||||
|
|
||||||
|
Go vanilla .text entropy: ~6.12. Modified builds with XOR padding: ~6.99 (borderline).
|
||||||
|
Stay below 7.0.
|
||||||
|
|
||||||
|
## Cloud Prediction (Second Tier)
|
||||||
|
|
||||||
|
- First 10,000 bytes of PE sent to cloud for second-tier ML
|
||||||
|
- Cloud model may be more aggressive than on-sensor
|
||||||
|
- Results cached in LRU (10 entries)
|
||||||
|
- Content beyond 10KB boundary invisible to cloud
|
||||||
|
|
||||||
|
## Signature Updates (BR Pass)
|
||||||
|
|
||||||
|
- 98 byte-pattern categories in BR00–BR61
|
||||||
|
- Patterns loaded from encrypted signature update files
|
||||||
|
- Updated daily/hourly — byte-pattern evasion is transient
|
||||||
|
- Structural evasion (reducing feature vector) is durable
|
||||||
|
|
||||||
|
## Model Scoring Math
|
||||||
|
|
||||||
|
### Leaf Weight Distribution (Bimodal)
|
||||||
|
|
||||||
|
```
|
||||||
|
[0.05, 0.10): 41 weights ( 8%) — "not sure" leaves (minimum weight 0.05)
|
||||||
|
[0.10, 2.00): 0 weights ( 0%) — NOTHING IN BETWEEN
|
||||||
|
[2.00, 2.26): 473 weights ( 92%) — "confident bad" leaves
|
||||||
|
```
|
||||||
|
|
||||||
|
The non-trivial leaf weights are bimodal. Trees also have zero-weight leaves (default
|
||||||
|
path when no indicator matched) — a feature that is NOT set produces a 0.0 contribution.
|
||||||
|
|
||||||
|
### Score Calculation
|
||||||
|
|
||||||
|
```
|
||||||
|
Score = sum of 20 leaf weights (one leaf per tree)
|
||||||
|
|
||||||
|
Zero features triggered: score 0.0 (all trees reach default zero-weight leaves)
|
||||||
|
All trees hit weak leaves: score ~1.0 (20 × 0.05)
|
||||||
|
All trees at max: score ~45.0 (20 × 2.25)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Score 0.0 = always passes.** Zero triggered indicators = zero model contribution.
|
||||||
|
|
||||||
|
### Estimated Detection Threshold
|
||||||
|
|
||||||
|
The relationship between triggered indicators and score is approximate — each tree
|
||||||
|
evaluates combinations of features (2–10 per path), not single indicators. But as
|
||||||
|
a rough model:
|
||||||
|
|
||||||
|
| Triggered indicators | Approx score | Detection? |
|
||||||
|
|---------------------:|-------------|------------|
|
||||||
|
| 0 | 0.0 | Clean |
|
||||||
|
| 5–8 | ~5–16 | Likely clean |
|
||||||
|
| 9 (Go vanilla) | ~10–20 | **Borderline** |
|
||||||
|
| 12–15 | ~15–30 | **LIKELY DETECTION THRESHOLD** |
|
||||||
|
| 16+ | ~20–45 | Detected |
|
||||||
|
|
||||||
|
A vanilla Go binary triggers 9 Re codes, which causes multiple trees to reach
|
||||||
|
"confident bad" leaves. Adding payload-specific indicators pushes more trees to
|
||||||
|
strong leaves. **Reducing total triggered indicators brings the score back toward
|
||||||
|
the clean range.**
|
||||||
|
|
||||||
|
**Important**: These are estimates. Each tree evaluates feature COMBINATIONS, not
|
||||||
|
single features. Two binaries with the same indicator count but different combinations
|
||||||
|
may score differently.
|
||||||
|
|
||||||
|
## Go Runtime → Re Code Mapping (Vanilla Baseline)
|
||||||
|
|
||||||
|
These Re indicators fire for a **vanilla Go binary** (unavoidable baseline):
|
||||||
|
|
||||||
|
| Re code | Importance | Triggered by | Required by Go runtime? |
|
||||||
|
|---------|-----------|--------------|------------------------|
|
||||||
|
| Re27 | **25** | GetThreadContext, SetThreadContext | **YES** — goroutine scheduling |
|
||||||
|
| Re01 | **22** | CreateToolhelp32Snapshot, Process32First/Next | **YES** — runtime process inspection |
|
||||||
|
| ReTe | **21** | GetStdHandle | **YES** — console I/O |
|
||||||
|
| Re05 | 0 | OpenProcessToken | Likely — token checks |
|
||||||
|
| Re19 | 0 | LoadLibraryExW | **YES** — dynamic loading |
|
||||||
|
| Re25 | 0 | TerminateProcess | **YES** — exit handling |
|
||||||
|
| Re28 | 0 | SuspendThread, ResumeThread | **YES** — goroutine scheduling |
|
||||||
|
| Re38 | 0 | VirtualProtect | **YES** — memory management |
|
||||||
|
| ReGq | 0 | GetQueuedCompletionStatusEx | **YES** — I/O completion ports |
|
||||||
|
|
||||||
|
**Total baseline Re importance: 68** (from 9 codes, mostly low-weight)
|
||||||
|
|
||||||
|
### Additional Re Codes from Payload Features
|
||||||
|
|
||||||
|
Each of these adds ~2.0 to the model score per triggered tree:
|
||||||
|
|
||||||
|
| Re code | Importance | APIs | Typical implant usage |
|
||||||
|
|---------|-----------|------|----------------------|
|
||||||
|
| Re17 | **21** | VirtualAllocEx | Cross-process memory allocation |
|
||||||
|
| Re18 | **21** | CreateRemoteThread | Thread injection |
|
||||||
|
| Re26 | **21** | OpenProcess | Process manipulation |
|
||||||
|
| Re09 | **21** | ReadProcessMemory | Memory reading |
|
||||||
|
| Re07 | **27** | CreateProcess, ShellExecute | Command execution |
|
||||||
|
| Re40 | **25** | CreateMutex | Single-instance check |
|
||||||
|
| Re03 | **22** | SetWindowsHookEx | Keylogging/hooking |
|
||||||
|
|
||||||
|
**Each additional Re code from this list pushes the score ~2.0 points higher.**
|
||||||
|
An implant adding Re17+Re18+Re26 adds ~6.0 to the score — potentially crossing
|
||||||
|
from borderline (score ~20) to detected (score ~26).
|
||||||
|
|
||||||
|
## BS (Binary Static) Feature Codes
|
||||||
|
|
||||||
|
90 Binary Static indicators at model features 494–583. These fire based on
|
||||||
|
the binary's structural properties (not imports). The specific conditions are
|
||||||
|
loaded from signature update files and update frequently.
|
||||||
|
|
||||||
|
Notable: BS codes are the second-largest indicator category (after BR byte patterns)
|
||||||
|
but their model importance is generally lower than Re codes because they occupy
|
||||||
|
features 494–583 which are outside the model's most-referenced range.
|
||||||
|
|
||||||
|
## Key Implications for Go Binary Optimization
|
||||||
|
|
||||||
|
1. **The model only penalizes, never rewards.** Fewer anomalies = lower score.
|
||||||
|
2. **Go vanilla is RIGHT at the detection boundary** (~9 strong trees out of 20).
|
||||||
|
Payload features push it over. Every indicator removed moves you back toward clean.
|
||||||
|
3. **Import table is the #1 controllable surface.** Prune DLLs aggressively.
|
||||||
|
Each removed Re code saves ~2.0 model score points.
|
||||||
|
4. **Don't fight the toolchain.** The BM pass checks PE metadata presence/absence — Go's
|
||||||
|
natural structure (16+ sections, numeric names, no Rich header) is consistent.
|
||||||
|
5. **File location matters.** FsHl (rank #4) penalizes %TEMP%/Downloads paths.
|
||||||
|
6. **Keep .text entropy < 7.0.** FPE0 fires above this threshold.
|
||||||
|
7. **Cloud sees first 10KB only.** Keep headers vanilla in this window.
|
||||||
|
8. **The model has NO moderate signals.** Each tree is either "confident bad" or
|
||||||
|
"not sure." There is no gradual scoring — indicators are binary on/off switches
|
||||||
|
that flip trees between 0.05 and 2.0+ contribution.
|
||||||
@@ -9,14 +9,17 @@ Before running any experiment, classify the proposed change. Category determines
|
|||||||
Changes that make the binary more consistent with a vanilla binary from the same toolchain.
|
Changes that make the binary more consistent with a vanilla binary from the same toolchain.
|
||||||
These reduce anomaly count without creating toolchain inconsistencies.
|
These reduce anomaly count without creating toolchain inconsistencies.
|
||||||
|
|
||||||
| Change | Target Feature | Expected Direction |
|
| Change | Target Feature | Expected Direction | On-sensor model feature |
|
||||||
|--------|---------------|-------------------|
|
|--------|---------------|-------------------|---------------------|
|
||||||
| Remove phantom .edata section | phantom_edata anomaly | -1 anomaly |
|
| Remove phantom .edata section | phantom_edata anomaly | -1 anomaly | BM07 (Export dir) |
|
||||||
| Revert stack reserve to Go default (0x200000) | stack_reserve inconsistency | -1 anomaly |
|
| Revert stack reserve to Go default (0x200000) | stack_reserve inconsistency | -1 anomaly | BM03-04 |
|
||||||
| Prune enriched DLLs to ≤3 | unused_import heuristic | -1 engine (AVG/Avast) |
|
| Prune enriched DLLs to ≤3 | unused_import heuristic | -1 engine (AVG/Avast) | **ReUM (rank #15)** |
|
||||||
| Remove risky DLL names (netapi32, ole32, winhttp) | AVG/Avast Evo-gen | Engine-specific |
|
| Remove risky DLL names (netapi32, ole32, winhttp) | AVG/Avast Evo-gen | Engine-specific | ReUM + specific Re codes |
|
||||||
| Prune risky product names from VERSIONINFO pool | product name corpus | Engine-specific |
|
| Prune risky product names from VERSIONINFO pool | product name corpus | Engine-specific | Te pass |
|
||||||
| Prune risky file versions | version ML feature | Engine-specific |
|
| Prune risky file versions | version ML feature | Engine-specific | Te pass |
|
||||||
|
| Remove CreateMutex imports if sync not needed | Re40 on-sensor indicator | **Model rank #7** | **Re40 (25 tree refs)** |
|
||||||
|
| Minimize total import DLL count | Import diversity score | **Model rank #15** | **ReUM (22 tree refs)** |
|
||||||
|
| Ensure .text entropy stays < 7.0 | Entropy threshold | Avoids FPE0 trigger | **FPE0 (feature 950)** |
|
||||||
|
|
||||||
**Expected behavior**: Lowers anomaly score, moves toward vanilla baseline delta.
|
**Expected behavior**: Lowers anomaly score, moves toward vanilla baseline delta.
|
||||||
**Risk**: Low — making things look more like vanilla Go is safe.
|
**Risk**: Low — making things look more like vanilla Go is safe.
|
||||||
|
|||||||
@@ -14,6 +14,26 @@ This means:
|
|||||||
- Multiple small anomalies that point in the same direction are detected with high confidence
|
- Multiple small anomalies that point in the same direction are detected with high confidence
|
||||||
- Fighting the toolchain identity adds inconsistency signals that raise the score
|
- Fighting the toolchain identity adds inconsistency signals that raise the score
|
||||||
|
|
||||||
|
## Verified On-Sensor Model Architecture
|
||||||
|
|
||||||
|
Reverse engineering of a major EDR vendor's kernel driver and on-sensor ML model
|
||||||
|
produced verified feature weights:
|
||||||
|
|
||||||
|
- **1,000-dimensional binary feature vector** — each feature is binary (present/absent)
|
||||||
|
- **20 gradient-boosted trees**, 8,976 nodes, ~9,100 leaf weights
|
||||||
|
- **Purely additive scoring** — ALL leaf weights positive (0.05–2.25), zero negative
|
||||||
|
- **10 static analysis passes** run on every file at kernel level:
|
||||||
|
BM (PE headers, 38 features), Re (imports, 43 features), BR (byte patterns, 98 features),
|
||||||
|
CR (content patterns, 18), cC (content category, 34), Te (strings, 24), FPE (entropy, 3),
|
||||||
|
Pes (sections, 3), Fs (filesystem, 11), AS (aggregates, 13)
|
||||||
|
- **BM pass uses zero/non-zero checks** — PE fields checked for presence, not value
|
||||||
|
- **BM12 flags single-section PEs, BM34 flags single-import PEs** — Go safe here
|
||||||
|
- **ReUM (import breadth) is rank #15** — import table diversity is penalized
|
||||||
|
- **FPE0 entropy threshold** inferred ~7.0 for code sections (macOS equivalent: 7.0–7.8)
|
||||||
|
- **Cloud model receives first 10KB only** — content beyond that boundary is invisible
|
||||||
|
- **Kernel driver does NOT import ZwProtectVirtualMemory** — memory protection changes invisible
|
||||||
|
- **Signature patterns (BR pass) update daily** — byte pattern evasion is transient
|
||||||
|
|
||||||
## Feature Category Reference
|
## Feature Category Reference
|
||||||
|
|
||||||
### 1. Header Fields (High ML Weight)
|
### 1. Header Fields (High ML Weight)
|
||||||
@@ -47,14 +67,25 @@ layout, import style, and string patterns still scream Go. Inconsistency = highe
|
|||||||
global variable allocation. Vanilla Go hello-world has 6.4x. This is load-bearing — cannot
|
global variable allocation. Vanilla Go hello-world has 6.4x. This is load-bearing — cannot
|
||||||
be easily changed without modifying the linker.
|
be easily changed without modifying the linker.
|
||||||
|
|
||||||
### 3. Import Table (High ML Weight)
|
### 3. Import Table (High ML Weight — Verified)
|
||||||
|
|
||||||
| Feature | Vanilla Go | Modified Build |
|
| Feature | Vanilla Go | Modified Build | On-sensor EDR feature |
|
||||||
|---------|-----------|-----------------|
|
|---------|-----------|-----------------|---------------------|
|
||||||
| DLL count | 1 | 4 |
|
| DLL count | 1 | 4 | **ReUM (rank #15)** — diversity penalized |
|
||||||
| Total imports | 47 | 53 |
|
| Total imports | 47 | 53 | Contributes to ReUM score |
|
||||||
| GetProcAddress present | Yes | Yes |
|
| GetProcAddress present | Yes | Yes | Re19 (MayLoadDynamicLibrary) |
|
||||||
| LoadLibraryExW present | Yes | Yes (duplicated) |
|
| LoadLibraryExW present | Yes | Yes (duplicated) | Re19 |
|
||||||
|
| CreateProcess* present | Via Go runtime | Via Go runtime | **Re07 (rank #3)** — highest-weight import |
|
||||||
|
| CreateMutex* present | If sync used | If sync used | **Re40 (rank #7)** — surprisingly high weight |
|
||||||
|
|
||||||
|
**The EDR's import analysis pass checks the IAT/ILT only.** APIs resolved via GetProcAddress
|
||||||
|
at runtime are invisible. The model has 43 import category indicators (Re01–Re43) plus ReUM (breadth).
|
||||||
|
|
||||||
|
**Import pruning priority by verified model weight:**
|
||||||
|
1. Remove any import triggering Re07 (CreateProcess) if not needed — rank #3
|
||||||
|
2. Remove any import triggering Re40 (CreateMutex) — rank #7, use events instead
|
||||||
|
3. Minimize total DLL count — ReUM (rank #15) penalizes breadth
|
||||||
|
4. Remove unused DLLs with 1-2 functions — AVG/Avast Evo-gen heuristic
|
||||||
|
|
||||||
**Unused import heuristic** (AVG/Avast Win64:Evo-gen): DLLs with only 1-2 functions that
|
**Unused import heuristic** (AVG/Avast Win64:Evo-gen): DLLs with only 1-2 functions that
|
||||||
are never called in the code trigger detection. Cap at 3 enriched DLLs max.
|
are never called in the code trigger detection. Cap at 3 enriched DLLs max.
|
||||||
|
|||||||
Reference in New Issue
Block a user