Compare commits
No commits in common. "v1.2.0" and "main" have entirely different histories.
35
.gitignore
vendored
35
.gitignore
vendored
@ -1,31 +1,36 @@
|
||||
# Build output
|
||||
**/bin/
|
||||
**/obj/
|
||||
**/publish/
|
||||
**/data/
|
||||
**/logs/
|
||||
**/dumps/
|
||||
**/reports/
|
||||
bin/
|
||||
obj/
|
||||
publish/
|
||||
*.exe
|
||||
*.dll
|
||||
*.pdb
|
||||
*.cache
|
||||
|
||||
# IDE
|
||||
.vs/
|
||||
.vscode/
|
||||
*.user
|
||||
*.suo
|
||||
.vscode/
|
||||
|
||||
# OS
|
||||
Thumbs.db
|
||||
Desktop.ini
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Python (legacy)
|
||||
# Logs / Data
|
||||
*.log
|
||||
data/
|
||||
logs/
|
||||
dumps/
|
||||
reports/
|
||||
settings.json
|
||||
crash.log
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
venv/
|
||||
dist/
|
||||
build/
|
||||
*.spec
|
||||
.venv/
|
||||
|
||||
# MCP
|
||||
ce-mcp-server/
|
||||
ce-mcp-server-http/
|
||||
|
||||
19
MemScanEDR.sln
Normal file
19
MemScanEDR.sln
Normal file
@ -0,0 +1,19 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MemScanEDR", "src-csharp\MemScanEDR\MemScanEDR.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|x64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|x64
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
154
README.md
154
README.md
@ -4,16 +4,27 @@
|
||||
|
||||
</div>
|
||||
|
||||
# MemScan-EDR
|
||||
# MemScan-EDR v2.0
|
||||
|
||||
> **开源、轻量化、内存专项的 Windows 终端威胁检测与响应 (EDR) 工具**
|
||||
> **AI 驱动的内存威胁检测引擎 — 三级联动检测管线**
|
||||
|
||||
[](LICENSE)
|
||||
[](https://dotnet.microsoft.com/)
|
||||
[](https://www.microsoft.com/windows)
|
||||
[](https://github.com/dotnet/wpf)
|
||||
|
||||
MemScan-EDR 扫描所有运行中的进程,检查内存中的 Shellcode / 注入 / C2 特征,并利用 AI(OpenAI / Claude / 本地模型)实时分类威胁。基于 C# WPF 构建,通过 `dotnet publish` 打包为单个 `.exe`,无需目标机器安装 .NET 运行时。
|
||||
MemScan-EDR v2.0 全面重构了病毒识别逻辑:**40 维内存特征向量 + 无监督基线检测 + 监督分类器 + MITRE ATT&CK 规则引擎** 三级联动,大幅降低误报并提升扫描速度。基于 C# WPF 构建,打包为单个 `.exe`,无需目标机器安装 .NET 运行时。
|
||||
|
||||
---
|
||||
|
||||
## v2.0 核心改进
|
||||
|
||||
| 维度 | v1.0 | v2.0 |
|
||||
|------|------|------|
|
||||
| **扫描方式** | 逐区域逐签名匹配保护位 | 预过滤 → 特征提取 → 三级 AI 判定 |
|
||||
| **特征维度** | 简单 (签名/保护/类型) | **40 维** (元数据 + 权限 + 代码 + 熵 + 网络 + DLL) |
|
||||
| **无监督检测** | 无 | **4 类进程基线库** (Z-score + 重构误差) |
|
||||
| **误报处理** | 系统提示词约束 LLM | **JIT/安全软件/脚本引擎** 白名单修正 |
|
||||
| **速度** | O(区域×签名) | **80% 进程预过滤跳过**,仅 20% 进 AI 管线 |
|
||||
|
||||
---
|
||||
|
||||
@ -23,65 +34,82 @@ MemScan-EDR 扫描所有运行中的进程,检查内存中的 Shellcode / 注
|
||||
|
||||
| 能力 | 描述 |
|
||||
|---|---|
|
||||
| **进程全量采集** | PID、进程名、路径、父进程树、启动命令行、数字签名、线程数、内存用量 |
|
||||
| **内存扫描** | `VirtualQueryEx` 遍历每个已提交内存区域;检测 RWX 私有内存、未映射可执行页 |
|
||||
| **签名匹配** | 12 条内置检测规则:NOP sled、CALL/POP EIP、ROR13 hash、CobaltStrike Beacon、Meterpreter、ReflectiveLoader、Mimikatz 等 |
|
||||
| **AI 威胁分类** | 多提供商支持(OpenAI 兼容 / Claude / Ollama / LM Studio)+ 启发式规则降级引擎 |
|
||||
| **CE-MCP 集成** | 自动启动 Cheat Engine MCP 服务器,提供内核级内存操作能力 |
|
||||
| **中英文界面切换** | 一键切换全部标签、按钮、告警文本 |
|
||||
| **单文件 EXE** | `dotnet publish` 生成自包含 `MemScan-EDR.exe`,目标机器无需 .NET 运行时 |
|
||||
| **40 维特征提取** | 内存页权限分布、PE 完整性、Shannon 熵、可疑 API/字符串、网络行为、DLL 加载链 |
|
||||
| **三级联动检测** | 第一层: 无监督基线异常 → 第二层: 监督分类器 → 第三层: MITRE ATT&CK 规则复核 |
|
||||
| **Process Hollowing 检测** | 内存 PE 头与磁盘文件比对,识别进程掏空攻击 |
|
||||
| **Shannon 熵分析** | 采样计算内存区域熵值,识别加密/压缩载荷 (高熵 > 7.0) |
|
||||
| **15 条 MITRE 规则** | T1055 进程注入、T1055.012 进程掏空、T1003 凭据转储、T1496 挖矿、T1486 勒索等 |
|
||||
| **15 条内置签名** | NOP sled、CobaltStrike、Meterpreter、Mimikatz、ReflectiveLoader 等 |
|
||||
| **智能误报修正** | JIT 运行时 (.NET/Java/Node.js)、安全软件、脚本引擎 (PowerShell)、开发工具 |
|
||||
| **扫描模式** | 快速 (元数据) / 平衡 (采样) / 深度 (全量) 三档可选 |
|
||||
| **LLM 辅助研判** | 可选开启大模型对可疑进程二次确认 |
|
||||
| **中英文界面** | 一键切换全部 UI 文本 |
|
||||
|
||||
### AI 提供商
|
||||
### 三级联动检测流程
|
||||
|
||||
| 提供商 | 描述 | Base URL 示例 |
|
||||
|---|---|---|
|
||||
| **OpenAI 兼容** | OpenAI / DeepSeek / 通义千问 / 智谱 等 | `https://api.openai.com/v1` |
|
||||
| **Claude** | Anthropic Claude Messages API | `https://api.anthropic.com` |
|
||||
| **本地模型** | Ollama / LM Studio / vLLM / llama.cpp | `http://127.0.0.1:11434` |
|
||||
| **启发式规则** | 内置规则引擎,无需 API Key | N/A |
|
||||
```
|
||||
[快速预过滤] → 跳过 >80% 已签名安全进程
|
||||
↓
|
||||
[第一层: 基线异常检测] → 40 维特征 Z-score + 重构误差
|
||||
↓ 完全匹配基线 → 直接安全
|
||||
[第二层: 监督分类器] → 加权线性模型 + 决策树规则
|
||||
↓ 良性 + 低基线异常 → 安全
|
||||
[第三层: MITRE 规则复核] → 15 条内存攻击规则 + 误报修正
|
||||
↓ AI + 基线 + 规则 三级交叉验证 → 最终判定
|
||||
```
|
||||
|
||||
### 内置签名
|
||||
### 内置签名 (15 条)
|
||||
|
||||
| 签名 | 类别 | 严重性 |
|
||||
|---|---|---|
|
||||
| `SHELLCODE_NOP_SLED` | Shellcode | 中 |
|
||||
| `SHELLCODE_GETEIP` | Shellcode | 中 |
|
||||
| `SHELLCODE_ROR13` | Shellcode | 中 |
|
||||
| `SHELLCODE_URLDOWNLOAD` | Shellcode | 中 |
|
||||
| `C2_COBALTSTRIKE` | C2 | 高 |
|
||||
| `C2_METERPRETER` | C2 | 中 |
|
||||
| `INJECTION_REFLECTIVE` | 注入 | 高 |
|
||||
| `INJECTION_CRT` | 注入 | 中 |
|
||||
| `INJECTION_VAE` | 注入 | 中 |
|
||||
| `PROC_HOLLOW` | 注入 | 高 |
|
||||
| `MALWARE_MIMIKATZ` | 恶意软件 | 高 |
|
||||
| `CRYPTO_MINER` | 挖矿 | 高 |
|
||||
| `RANSOM_NOTE` | 恶意软件 | 高 |
|
||||
| `PS_DOWNLOAD` | 恶意软件 | 中 |
|
||||
| `KEYLOGGER_API` | 恶意软件 | 中 |
|
||||
|
||||
---
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
TitleBar ── 窗口控制(最小化/最大化/关闭,语言切换)
|
||||
Sidebar ── 导航(仪表盘 / 进程 / 扫描 / 设置)
|
||||
Content ── 4 个视图(WPF Grid Visibility 切换)
|
||||
Models:
|
||||
MemoryFeatures ── 40 维 AI 特征向量
|
||||
BaselineProfile ── 正常程序基线 (4 类进程)
|
||||
ProcessInfo ── 进程元数据 + 网络 + 内存区域
|
||||
|
||||
服务层:
|
||||
ProcessCollector ── CreateToolhelp32Snapshot + WMI → ProcessInfo[]
|
||||
MemoryScanner ── VirtualQueryEx + 签名遍历 → SignatureMatch[]
|
||||
AIAnalyzer ── HTTP → OpenAI/Claude/Ollama → ScanResult
|
||||
CeMcpManager ── Process.Start → python server.py
|
||||
Services:
|
||||
FeatureExtractor ── 从进程内存提取全维度特征
|
||||
EntropyAnalyzer ── Shannon 熵计算 (采样优化)
|
||||
PEHeaderAnalyzer ── PE 完整性验证 + Hollowing 检测
|
||||
BaselineEngine ── 无监督基线异常检测 (Z-score + 重构误差)
|
||||
ClassificationEngine ── 监督分类器 (加权 + 决策树)
|
||||
RuleEngine ── 15 条 MITRE ATT&CK 内存攻击规则
|
||||
DetectionPipeline ── 三级联动编排引擎
|
||||
AIAnalyzer ── 集成管线 + LLM 辅助研判
|
||||
MemoryScanner ── 实际字节匹配 + 按需扫描
|
||||
ProcessCollector ── 进程枚举 + 模块列表采集
|
||||
```
|
||||
|
||||
数据流:
|
||||
```
|
||||
[开始扫描] → ProcessCollector.CollectAll()
|
||||
→ 每个进程:
|
||||
→ MemoryScanner.ScanProcess() → 签名匹配
|
||||
→ MemoryScanner.GetSuspiciousRegions() → RWX 私有区域
|
||||
→ QuickPreScore() — score < 0.05 跳过 API
|
||||
→ AIAnalyzer.AnalyzeProcessAsync() → ScanResult
|
||||
→ IProgress<> 实时更新界面
|
||||
→ 快速预过滤 (不读内存, 跳过 >80% 进程)
|
||||
→ FeatureExtractor.Extract() → MemoryFeatures (40 维向量)
|
||||
→ 第一层: BaselineEngine.Analyze() → 基线异常分数
|
||||
→ 第二层: ClassificationEngine.Classify() → 恶意/可疑/良性
|
||||
→ 第三层: RuleEngine.Validate() → MITRE 规则 + 误报修正
|
||||
→ ScanResult → IProgress<> 实时更新界面
|
||||
```
|
||||
|
||||
---
|
||||
@ -98,8 +126,6 @@ Content ── 4 个视图(WPF Grid Visibility 切换)
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/Huang-158/MemScan-EDR.git
|
||||
# 或从 Gitea 镜像克隆
|
||||
# git clone http://39.96.40.167/Huang-158/MemScan-EDR.git
|
||||
cd MemScan-EDR\src-csharp
|
||||
|
||||
# Debug 编译
|
||||
@ -146,22 +172,29 @@ dotnet publish MemScanEDR\MemScanEDR.csproj -c Release -r win-x64 --self-contain
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
MemScan-EDR/
|
||||
MemScan-EDR-2.0/
|
||||
├── src-csharp/
|
||||
│ ├── MemScanEDR.sln
|
||||
│ └── MemScanEDR/
|
||||
│ ├── MemScanEDR.csproj
|
||||
│ ├── App.xaml / App.xaml.cs
|
||||
│ ├── MainWindow.xaml / MainWindow.xaml.cs
|
||||
│ ├── Models/
|
||||
│ │ ├── MemoryFeatures.cs ★ 40 维 AI 特征向量
|
||||
│ │ ├── BaselineProfile.cs ★ 正常程序基线库
|
||||
│ │ ├── ProcessInfo.cs
|
||||
│ │ ├── AIConfig.cs
|
||||
│ │ └── AppSettings.cs
|
||||
│ └── Services/
|
||||
│ ├── ProcessCollector.cs
|
||||
│ ├── MemoryScanner.cs
|
||||
│ ├── AIAnalyzer.cs
|
||||
│ └── CeMcpManager.cs
|
||||
│ ├── FeatureExtractor.cs ★ 核心特征提取器
|
||||
│ ├── EntropyAnalyzer.cs ★ Shannon 熵分析
|
||||
│ ├── PEHeaderAnalyzer.cs ★ PE 完整性验证
|
||||
│ ├── BaselineEngine.cs ★ 无监督基线检测
|
||||
│ ├── ClassificationEngine.cs★ 监督分类器
|
||||
│ ├── RuleEngine.cs ★ MITRE ATT&CK 规则
|
||||
│ ├── DetectionPipeline.cs ★ 三级联动管线
|
||||
│ ├── AIAnalyzer.cs (集成管线 + LLM)
|
||||
│ ├── MemoryScanner.cs (优化版字节匹配)
|
||||
│ └── ProcessCollector.cs
|
||||
├── rules/
|
||||
│ ├── shellcode.yar
|
||||
│ ├── injection.yar
|
||||
@ -169,34 +202,37 @@ MemScan-EDR/
|
||||
├── scripts/
|
||||
│ └── build_csharp.bat
|
||||
├── LICENSE
|
||||
├── README.md # 中文文档(默认)
|
||||
└── README_EN.md # English docs
|
||||
├── README.md
|
||||
└── README_EN.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 待办事项
|
||||
## 快速开始
|
||||
|
||||
- [ ] 1. YARA 规则文件加载与匹配引擎
|
||||
- [ ] 2. 网络连接 PID 关联(TCP 连接→进程)
|
||||
- [ ] 3. 内存 Dump 与 PE 结构解析
|
||||
- [ ] 4. 审计日志持久化
|
||||
- [ ] 5. 报告导出(PDF / HTML)
|
||||
- [ ] 6. 定时扫描调度
|
||||
- [ ] 7. 高危进程一键终止
|
||||
- [ ] 8. 隐藏进程检测(EPROCESS 链表)
|
||||
- [x] 9. 多 AI 提供商 + 防误报(v1.2)
|
||||
- [x] 10. CE-MCP 自动启动(v1.1)
|
||||
- [x] 11. 中英文界面切换(v1.1)
|
||||
- [x] 12. 12 条内置内存签名(v1.0)
|
||||
- [x] 13. WPF 白色主题界面(v1.0)
|
||||
### 环境要求
|
||||
|
||||
- Windows 10 / 11 (x64)
|
||||
- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
|
||||
- 管理员权限(用于内存扫描)
|
||||
|
||||
### 编译构建
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/Huang-158/MemScan-EDR.git
|
||||
cd MemScan-EDR
|
||||
|
||||
# 发布单文件 EXE
|
||||
dotnet publish src-csharp/MemScanEDR/MemScanEDR.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
|
||||
|
||||
# 运行
|
||||
.\publish\MemScan-EDR.exe
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 声明
|
||||
|
||||
> 本项目与 Cheat Engine、其原作者 Dark Byte 以及任何 AI 提供商(OpenAI、Anthropic 等)无任何关联。
|
||||
>
|
||||
> 本工具仅用于**合法的安全研究、教育及授权的安全评估**。未经授权对非自有或未被许可的系统使用属违法行为。使用者须遵守当地法律法规并自行承担所有风险。
|
||||
|
||||
---
|
||||
|
||||
194
README_EN.md
194
README_EN.md
@ -4,84 +4,79 @@
|
||||
|
||||
</div>
|
||||
|
||||
# MemScan-EDR
|
||||
# MemScan-EDR v2.0
|
||||
|
||||
> **Open-Source, Lightweight, Memory-Focused Windows Endpoint Detection & Response (EDR) Tool**
|
||||
> **AI-Powered Memory Threat Detection Engine with 3-Layer Detection Pipeline**
|
||||
|
||||
[](LICENSE)
|
||||
[](https://dotnet.microsoft.com/)
|
||||
[](https://www.microsoft.com/windows)
|
||||
[](https://github.com/dotnet/wpf)
|
||||
|
||||
MemScan-EDR scans all running processes, inspects memory for shellcode/injection/C2 patterns, and uses AI (OpenAI / Claude / local models) to classify threats in real time. Built with C# WPF for zero-dependency deployment as a single `.exe`.
|
||||
MemScan-EDR v2.0 fully refactors the threat detection logic: **40-dimensional memory feature vector + unsupervised baseline detection + supervised classifier + MITRE ATT&CK rule engine** in a 3-layer pipeline, significantly reducing false positives and improving scan speed. Built with C# WPF, packaged as a single `.exe`.
|
||||
|
||||
---
|
||||
|
||||
## v2.0 Key Improvements
|
||||
|
||||
| Dimension | v1.0 | v2.0 |
|
||||
|-----------|------|------|
|
||||
| **Scan Method** | Per-region signature matching | Prefilter → Feature extraction → 3-layer AI |
|
||||
| **Features** | Simple (sig/protect/type) | **40-dim** (metadata + perms + code + entropy + network + DLL) |
|
||||
| **Unsupervised** | None | **4-class process baseline** (Z-score + reconstruction error) |
|
||||
| **False Positives** | System prompt constraints | **JIT/AV/scripting engine** whitelist corrections |
|
||||
| **Speed** | O(regions x signatures) | **80% prefilters skipped**, only 20% enter AI pipeline |
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Core Capabilities
|
||||
|
||||
| Capability | Description |
|
||||
|---|---|
|
||||
| **Process Enumeration** | Full process list with PID, name, path, parent tree, command line, digital signature, thread count, memory usage |
|
||||
| **Memory Scanning** | `VirtualQueryEx` traversal of every committed region; detects RWX private memory, unbacked executable pages |
|
||||
| **Signature Matching** | 12 built-in detection rules: NOP sled, CALL/POP EIP, ROR13 hash, CobaltStrike Beacon, Meterpreter, ReflectiveLoader, Mimikatz, etc. |
|
||||
| **AI Threat Classification** | Multi-provider support (OpenAI-compatible / Claude / Ollama / LM Studio) + heuristic fallback engine |
|
||||
| **CE-MCP Integration** | Auto-launch Cheat Engine MCP server for kernel-level memory operations |
|
||||
| **Chinese / English UI** | One-click language toggle; all labels, buttons, and alerts switch instantly |
|
||||
| **Single-File EXE** | `dotnet publish` produces a self-contained `MemScan-EDR.exe` — no .NET runtime needed on target |
|
||||
| **40-Dim Feature Extraction** | Memory page permissions, PE integrity, Shannon entropy, suspicious APIs/strings, network behavior, DLL loading chain |
|
||||
| **3-Layer Detection** | Layer 1: Unsupervised baseline → Layer 2: Supervised classifier → Layer 3: MITRE ATT&CK rules |
|
||||
| **Process Hollowing Detection** | Memory PE header vs disk file comparison |
|
||||
| **Shannon Entropy Analysis** | Identify encrypted/compressed payloads (high entropy > 7.0) |
|
||||
| **15 MITRE Rules** | T1055 injection, T1055.012 hollowing, T1003 credential dump, T1496 mining, T1486 ransomware, etc. |
|
||||
| **15 Built-in Signatures** | NOP sled, CobaltStrike, Meterpreter, Mimikatz, ReflectiveLoader, etc. |
|
||||
| **Smart FP Correction** | JIT runtimes (.NET/Java/Node.js), security software, scripting engines (PowerShell), dev tools |
|
||||
| **Scan Modes** | Fast (metadata) / Balanced (sampled) / Deep (full memory) |
|
||||
| **LLM Assist** | Optional LLM second opinion for suspicious processes |
|
||||
| **Chinese/English UI** | One-click language toggle |
|
||||
|
||||
### AI Providers
|
||||
### 3-Layer Detection Pipeline
|
||||
|
||||
| Provider | Description | Base URL Example |
|
||||
|---|---|---|
|
||||
| **OpenAI-Compatible** | OpenAI / DeepSeek / Qwen / Zhipu / any OpenAI-format API | `https://api.openai.com/v1` |
|
||||
| **Claude** | Anthropic Claude via Messages API | `https://api.anthropic.com` |
|
||||
| **Local** | Ollama / LM Studio / vLLM / llama.cpp server | `http://127.0.0.1:11434` |
|
||||
| **Heuristic** | Built-in rule engine (no API key needed) | N/A |
|
||||
|
||||
### Built-in Signatures
|
||||
|
||||
| Signature | Category | Severity |
|
||||
|---|---|---|
|
||||
| `SHELLCODE_NOP_SLED` | Shellcode | Medium |
|
||||
| `SHELLCODE_GETEIP` | Shellcode | Medium |
|
||||
| `SHELLCODE_ROR13` | Shellcode | Medium |
|
||||
| `C2_COBALTSTRIKE` | C2 | High |
|
||||
| `C2_METERPRETER` | C2 | Medium |
|
||||
| `INJECTION_REFLECTIVE` | Injection | High |
|
||||
| `INJECTION_CRT` | Injection | Medium |
|
||||
| `INJECTION_VAE` | Injection | Medium |
|
||||
| `MALWARE_MIMIKATZ` | Malware | High |
|
||||
| `CRYPTO_MINER` | Crypto | High |
|
||||
| `RANSOM_NOTE` | Malware | High |
|
||||
| `PS_DOWNLOAD` | Malware | Medium |
|
||||
```
|
||||
[Fast Prefilter] → Skip >80% signed safe processes
|
||||
↓
|
||||
[Layer 1: Baseline Anomaly] → 40-dim Z-score + reconstruction error
|
||||
↓ Matches baseline → Safe
|
||||
[Layer 2: Supervised Classifier] → Weighted linear model + decision tree
|
||||
↓ Benign + low anomaly → Safe
|
||||
[Layer 3: MITRE Rule Review] → 15 memory attack rules + FP corrections
|
||||
↓ AI + Baseline + Rules cross-validation → Final verdict
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
TitleBar ── Window Chrome (Min/Max/Close, Language Toggle)
|
||||
Sidebar ── Navigation (Dashboard / Processes / Scan / Settings)
|
||||
Content ── 4 Views (WPF Grid Visibility switching)
|
||||
Models:
|
||||
MemoryFeatures ── 40-dim AI feature vector
|
||||
BaselineProfile ── Normal process baselines (4 classes)
|
||||
ProcessInfo ── Process metadata + network + memory regions
|
||||
|
||||
Services:
|
||||
ProcessCollector ── CreateToolhelp32Snapshot + WMI → ProcessInfo[]
|
||||
MemoryScanner ── VirtualQueryEx + signature walk → SignatureMatch[]
|
||||
AIAnalyzer ── HTTP → OpenAI/Claude/Ollama API → ScanResult
|
||||
CeMcpManager ── Process.Start → python server.py
|
||||
```
|
||||
|
||||
Data flow:
|
||||
```
|
||||
[Start Scan] → ProcessCollector.CollectAll()
|
||||
→ for each ProcessInfo:
|
||||
→ MemoryScanner.ScanProcess() → signature matches
|
||||
→ MemoryScanner.GetSuspiciousRegions() → RWX private regions
|
||||
→ QuickPreScore() — skip API if score < 0.05
|
||||
→ AIAnalyzer.AnalyzeProcessAsync() → ScanResult
|
||||
→ Update DataGrid / Stats in real-time via IProgress<>
|
||||
FeatureExtractor ── Extract all-dimensional features from process memory
|
||||
EntropyAnalyzer ── Shannon entropy calculation (sampling optimized)
|
||||
PEHeaderAnalyzer ── PE integrity verification + Hollowing detection
|
||||
BaselineEngine ── Unsupervised baseline anomaly detection
|
||||
ClassificationEngine ── Supervised classifier (weighted + decision tree)
|
||||
RuleEngine ── 15 MITRE ATT&CK memory attack rules
|
||||
DetectionPipeline ── 3-layer orchestration engine
|
||||
AIAnalyzer ── Pipeline integration + LLM assist
|
||||
MemoryScanner ── Real byte matching + on-demand scanning
|
||||
ProcessCollector ── Process enumeration + module listing
|
||||
```
|
||||
|
||||
---
|
||||
@ -98,102 +93,17 @@ Data flow:
|
||||
|
||||
```powershell
|
||||
git clone https://github.com/Huang-158/MemScan-EDR.git
|
||||
# Or clone from Gitea mirror
|
||||
# git clone http://39.96.40.167/Huang-158/MemScan-EDR.git
|
||||
cd MemScan-EDR\src-csharp
|
||||
cd MemScan-EDR
|
||||
|
||||
dotnet build MemScanEDR.sln
|
||||
|
||||
dotnet publish MemScanEDR\MemScanEDR.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
|
||||
dotnet publish src-csharp/MemScanEDR/MemScanEDR.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o publish
|
||||
|
||||
.\publish\MemScan-EDR.exe
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```powershell
|
||||
.\scripts\build_csharp.bat
|
||||
```
|
||||
|
||||
### CE-MCP Setup (Optional)
|
||||
|
||||
1. Place [ce-mcp-server](https://github.com/Huang-158/ce-mcp-server) next to the project
|
||||
2. Or configure the path in Settings → CE-MCP Server Path
|
||||
3. Set to `disabled` to run without CE-MCP
|
||||
|
||||
### AI Configuration
|
||||
|
||||
1. Select provider: Heuristic / OpenAI-Compatible / Claude / Local
|
||||
2. Enter API Base URL
|
||||
3. Enter API Key (stored locally)
|
||||
4. Enter Model name
|
||||
5. Click Save
|
||||
|
||||
---
|
||||
|
||||
## How AI Analysis Works
|
||||
|
||||
1. **Pre-filter**: Clean processes (signed, system path, no RWX, no sig matches) → `Safe`, skip API
|
||||
2. **Structured Prompt**: Each process gets metadata, connections, signatures, suspicious regions
|
||||
3. **Safe-by-Default**: AI told >95% are benign; needs multiple concurring indicators
|
||||
4. **Heuristic Fallback**: Conservative thresholds (≥0.50 suspicious, ≥0.75 high) when API is down
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
MemScan-EDR/
|
||||
├── src-csharp/
|
||||
│ ├── MemScanEDR.sln
|
||||
│ └── MemScanEDR/
|
||||
│ ├── MemScanEDR.csproj
|
||||
│ ├── App.xaml / App.xaml.cs
|
||||
│ ├── MainWindow.xaml / MainWindow.xaml.cs
|
||||
│ ├── Models/
|
||||
│ │ ├── ProcessInfo.cs
|
||||
│ │ ├── AIConfig.cs
|
||||
│ │ └── AppSettings.cs
|
||||
│ └── Services/
|
||||
│ ├── ProcessCollector.cs
|
||||
│ ├── MemoryScanner.cs
|
||||
│ ├── AIAnalyzer.cs
|
||||
│ └── CeMcpManager.cs
|
||||
├── rules/
|
||||
│ ├── shellcode.yar
|
||||
│ ├── injection.yar
|
||||
│ └── malware_strings.yar
|
||||
├── scripts/
|
||||
│ └── build_csharp.bat
|
||||
├── LICENSE
|
||||
├── README.md # 中文文档(默认)
|
||||
└── README_EN.md # English docs (current)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TODO
|
||||
|
||||
- [ ] 1. YARA rule file loading & matching engine
|
||||
- [ ] 2. TCP connection PID association
|
||||
- [ ] 3. Memory dump & PE structure analysis
|
||||
- [ ] 4. Audit log persistence
|
||||
- [ ] 5. Report export (PDF / HTML)
|
||||
- [ ] 6. Scheduled scan
|
||||
- [ ] 7. One-click terminate high-risk processes
|
||||
- [ ] 8. Hidden process detection (EPROCESS walk)
|
||||
- [x] 9. Multi AI provider + anti-false-positive (v1.2)
|
||||
- [x] 10. CE-MCP auto-start (v1.1)
|
||||
- [x] 11. Chinese/English UI toggle (v1.1)
|
||||
- [x] 12. 12 built-in memory signatures (v1.0)
|
||||
- [x] 13. WPF white-theme UI (v1.0)
|
||||
|
||||
---
|
||||
|
||||
## Disclaimer
|
||||
|
||||
> This project is not affiliated with Cheat Engine, Dark Byte, or any AI provider (OpenAI, Anthropic, etc.).
|
||||
>
|
||||
> This tool is intended for **legitimate security research, education, and authorized security assessments only**. Unauthorized use is illegal. Users are responsible for complying with all applicable laws.
|
||||
|
||||
---
|
||||
|
||||
@ -1 +0,0 @@
|
||||
|
||||
16
scripts/README.md
Normal file
16
scripts/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# MemScan-EDR v2.0 - Build & Tools
|
||||
|
||||
This directory contains build scripts and utility tools for MemScan-EDR v2.0.
|
||||
|
||||
## Build Scripts
|
||||
|
||||
- `build_csharp.bat` - One-click build for the C# WPF application (Windows x64, self-contained single-file EXE)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- .NET 8 SDK
|
||||
- Windows 10/11 x64 (for building and running)
|
||||
|
||||
## Usage
|
||||
|
||||
Run `build_csharp.bat` from this directory to build the full application.
|
||||
@ -1,61 +1,48 @@
|
||||
@echo off
|
||||
setlocal
|
||||
|
||||
cd /d "%~dp0.."
|
||||
|
||||
echo =======================================================
|
||||
echo MemScan-EDR C# Build
|
||||
echo =======================================================
|
||||
chcp 65001 >nul
|
||||
echo ============================================
|
||||
echo MemScan-EDR v2.0 - Build Script
|
||||
echo AI-Powered Memory Detection
|
||||
echo ============================================
|
||||
echo.
|
||||
|
||||
:: Check .NET 8 SDK
|
||||
where dotnet >nul 2>&1
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo [ERROR] .NET SDK not found!
|
||||
echo.
|
||||
echo Install .NET 8 SDK from:
|
||||
echo https://dotnet.microsoft.com/download/dotnet/8.0
|
||||
echo.
|
||||
echo [ERROR] .NET SDK not found. Please install .NET 8 SDK.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
dotnet --version
|
||||
echo.
|
||||
|
||||
echo [1/3] Restoring NuGet packages...
|
||||
dotnet restore src-csharp\MemScanEDR.sln
|
||||
dotnet restore src-csharp/MemScanEDR/MemScanEDR.csproj
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo [FAIL] Restore failed
|
||||
echo [ERROR] Restore failed.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [2/3] Building Release (single-file exe)...
|
||||
dotnet publish src-csharp\MemScanEDR\MemScanEDR.csproj -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true -o dist-csharp
|
||||
echo [2/3] Building Release (win-x64, self-contained)...
|
||||
dotnet publish src-csharp/MemScanEDR/MemScanEDR.csproj ^
|
||||
-c Release ^
|
||||
-r win-x64 ^
|
||||
--self-contained true ^
|
||||
-p:PublishSingleFile=true ^
|
||||
-p:IncludeNativeLibrariesForSelfExtract=true ^
|
||||
-o publish
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo [FAIL] Build failed
|
||||
echo [ERROR] Build failed.
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [3/3] Copying resources...
|
||||
if exist "rules" xcopy /E /I /Y "rules" "dist-csharp\rules" >nul
|
||||
if exist "config" xcopy /E /I /Y "config" "dist-csharp\config" >nul
|
||||
if exist "src\ce_mcp_server\server.py" copy /Y "src\ce_mcp_server\server.py" "dist-csharp\src\ce_mcp_server\server.py" >nul
|
||||
echo [3/3] Copying assets...
|
||||
if exist "rules" xcopy /E /I /Y "rules" "publish\rules\" >nul
|
||||
if exist "..\MemScan-EDR\rules" xcopy /E /I /Y "..\MemScan-EDR\rules" "publish\rules\" >nul
|
||||
|
||||
echo.
|
||||
echo =======================================================
|
||||
echo BUILD SUCCESS!
|
||||
echo.
|
||||
if exist "dist-csharp\MemScan-EDR.exe" (
|
||||
echo Output: dist-csharp\MemScan-EDR.exe
|
||||
dir "dist-csharp\MemScan-EDR.exe" 2>nul | findstr "MemScan"
|
||||
)
|
||||
echo.
|
||||
echo Copy dist-csharp\ folder to target machine.
|
||||
echo Requires .NET 8 runtime OR self-contained exe.
|
||||
echo =======================================================
|
||||
echo.
|
||||
echo ============================================
|
||||
echo BUILD SUCCESSFUL!
|
||||
echo Output: publish\MemScan-EDR.exe
|
||||
echo ============================================
|
||||
pause
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MemScanEDR", "MemScanEDR\MemScanEDR.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@ -32,7 +32,7 @@ namespace MemScanEDR
|
||||
private void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
|
||||
{
|
||||
LogError("UI Exception", e.Exception);
|
||||
MessageBox.Show($"Error logged to crash.log:\n\n{e.Exception.Message}", "MemScan-EDR",
|
||||
MessageBox.Show($"Error logged to crash.log:\n\n{e.Exception.Message}", "MemScan-EDR v2.0",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
e.Handled = true;
|
||||
}
|
||||
@ -52,4 +52,4 @@ namespace MemScanEDR
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:MemScanEDR"
|
||||
Title="MemScan-EDR - Endpoint Detection & Response"
|
||||
Title="MemScan-EDR v2.0 - AI Memory Detection"
|
||||
Height="750" Width="1200"
|
||||
MinHeight="650" MinWidth="1000"
|
||||
WindowStartupLocation="CenterScreen"
|
||||
@ -23,6 +23,7 @@
|
||||
<Color x:Key="AccentBlue">#3b82f6</Color>
|
||||
<Color x:Key="AccentGreen">#22c55e</Color>
|
||||
<Color x:Key="AccentYellow">#f59e0b</Color>
|
||||
<Color x:Key="AccentPurple">#8b5cf6</Color>
|
||||
<Color x:Key="HighlightBlue">#eff6ff</Color>
|
||||
<Color x:Key="SidebarText">#cbd5e1</Color>
|
||||
<Color x:Key="SidebarTextDim">#64748b</Color>
|
||||
@ -41,12 +42,15 @@
|
||||
<SolidColorBrush x:Key="AccentBlueBrush" Color="{StaticResource AccentBlue}"/>
|
||||
<SolidColorBrush x:Key="AccentGreenBrush" Color="{StaticResource AccentGreen}"/>
|
||||
<SolidColorBrush x:Key="AccentYellowBrush" Color="{StaticResource AccentYellow}"/>
|
||||
<SolidColorBrush x:Key="AccentPurpleBrush" Color="{StaticResource AccentPurple}"/>
|
||||
<SolidColorBrush x:Key="HighlightBlueBrush" Color="{StaticResource HighlightBlue}"/>
|
||||
<SolidColorBrush x:Key="SidebarTextBrush" Color="{StaticResource SidebarText}"/>
|
||||
<SolidColorBrush x:Key="SidebarTextDimBrush" Color="{StaticResource SidebarTextDim}"/>
|
||||
<SolidColorBrush x:Key="SidebarHighlightBrush" Color="{StaticResource SidebarHighlight}"/>
|
||||
|
||||
<local:ListToStringConverter x:Key="ListToStringConv"/>
|
||||
<local:RiskLevelToColorConverter x:Key="RiskColorConv"/>
|
||||
<local:RiskLevelToTextConverter x:Key="RiskTextConv"/>
|
||||
|
||||
<!-- Nav Button -->
|
||||
<Style x:Key="NavBtn" TargetType="RadioButton">
|
||||
@ -158,10 +162,10 @@
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="Auto"/><ColumnDefinition Width="*"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal" Margin="14,0,0,0" VerticalAlignment="Center">
|
||||
<Ellipse Width="9" Height="9" Fill="{StaticResource AccentBlueBrush}" Margin="0,0,8,0"/>
|
||||
<TextBlock Text="MemScan-EDR" Foreground="{StaticResource TextSecondaryBrush}" FontSize="11.5" VerticalAlignment="Center"/>
|
||||
<Ellipse Width="9" Height="9" Fill="{StaticResource AccentPurpleBrush}" Margin="0,0,8,0"/>
|
||||
<TextBlock Text="MemScan-EDR v2.0" Foreground="{StaticResource TextSecondaryBrush}" FontSize="11.5" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
<TextBlock Grid.Column="1" Text="v1.0" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
<TextBlock Grid.Column="1" Text="AI-Powered Memory Detection" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
Foreground="{StaticResource TextDimBrush}" FontSize="11"/>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" Margin="0,0,4,0">
|
||||
<Button x:Name="BtnLang" Content="中" Width="32" Height="24" Click="BtnLangToggle_Click"
|
||||
@ -191,12 +195,12 @@
|
||||
<StackPanel Grid.Row="0" Margin="20,18,20,6">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Border Width="34" Height="34" CornerRadius="10" Background="#334155">
|
||||
<TextBlock Text="S" FontSize="17" FontWeight="Bold" Foreground="{StaticResource AccentBlueBrush}"
|
||||
<TextBlock Text="AI" FontSize="17" FontWeight="Bold" Foreground="{StaticResource AccentPurpleBrush}"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||||
</Border>
|
||||
<StackPanel Margin="10,0,0,0" VerticalAlignment="Center">
|
||||
<TextBlock Text="MemScan" FontSize="17" FontWeight="Bold" Foreground="White"/>
|
||||
<TextBlock Text="EDR v1.0" FontSize="11" Foreground="{StaticResource SidebarTextDimBrush}"/>
|
||||
<TextBlock Text="EDR v2.0" FontSize="11" Foreground="{StaticResource SidebarTextDimBrush}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
@ -234,8 +238,8 @@
|
||||
<Border Background="#334155" CornerRadius="10" Padding="12,10">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="TbQuickScan" Text="快速扫描" FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource AccentRedBrush}"/>
|
||||
<TextBlock x:Name="TbQuickScanDesc" Text="立即扫描所有进程" FontSize="10.5"
|
||||
Foreground="{StaticResource AccentPurpleBrush}"/>
|
||||
<TextBlock x:Name="TbQuickScanDesc" Text="AI 管线分析所有进程" FontSize="10.5"
|
||||
Foreground="{StaticResource SidebarTextDimBrush}" Margin="0,4,0,8"/>
|
||||
<Button x:Name="BtnStartScan" Content="开始扫描" Style="{StaticResource PrimaryBtn}"
|
||||
Height="34" FontSize="12" Click="BtnStartScan_Click"/>
|
||||
@ -256,7 +260,7 @@
|
||||
<TextBlock x:Name="TbDashboardTitle" Text="安全仪表盘" FontSize="20" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
<Border Background="{StaticResource HighlightBlueBrush}" CornerRadius="10" Padding="10,3" Margin="14,0,0,0" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="LblDashboardStatus" Text="● 监控中" FontSize="10.5" FontWeight="SemiBold"
|
||||
<TextBlock x:Name="LblDashboardStatus" Text="● AI 监控中" FontSize="10.5" FontWeight="SemiBold"
|
||||
Foreground="{StaticResource AccentBlueBrush}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
@ -310,8 +314,8 @@
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Ellipse Width="8" Height="8" Margin="0,0,6,0" Fill="{StaticResource AccentGreenBrush}"/>
|
||||
<TextBlock Text="{Binding RiskLevel}" FontSize="11.5" VerticalAlignment="Center"/>
|
||||
<Ellipse Width="8" Height="8" Margin="0,0,6,0" Fill="{Binding RiskLevel, Converter={StaticResource RiskColorConv}}"/>
|
||||
<TextBlock Text="{Binding RiskLevel, Converter={StaticResource RiskTextConv}}" FontSize="11.5" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@ -319,7 +323,8 @@
|
||||
<DataGridTextColumn Header="PID" Binding="{Binding Pid}" Width="55"/>
|
||||
<DataGridTextColumn Header="PROCESS" Binding="{Binding ProcessName}" Width="*" MinWidth="120"/>
|
||||
<DataGridTextColumn Header="SCORE" Binding="{Binding RiskScore, StringFormat={}{0:F2}}" Width="60"/>
|
||||
<DataGridTextColumn Header="PROVIDER" Binding="{Binding AIProvider}" Width="130"/>
|
||||
<DataGridTextColumn Header="LAYER" Binding="{Binding DetectionLayer}" Width="120"/>
|
||||
<DataGridTextColumn Header="PROVIDER" Binding="{Binding AIProvider}" Width="140"/>
|
||||
<DataGridTextColumn Header="TIME" Binding="{Binding ScanTime, StringFormat=HH:mm:ss}" Width="70"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
@ -330,7 +335,7 @@
|
||||
<Grid x:Name="ProcessView" Margin="24,18,24,20" Visibility="Collapsed">
|
||||
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,14">
|
||||
<TextBlock x:Name="TbProcTitle" Text="扫描进程" FontSize="20" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
<TextBlock x:Name="TbProcTitle" Text="AI 扫描结果" FontSize="20" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
<TextBlock x:Name="LblProcCount" Text=" - 0 条结果" FontSize="13" Foreground="{StaticResource TextDimBrush}" VerticalAlignment="Center" Margin="8,0,0,0"/>
|
||||
</StackPanel>
|
||||
<DataGrid x:Name="DgProcesses" Grid.Row="1" AutoGenerateColumns="False" IsReadOnly="True"
|
||||
@ -346,8 +351,8 @@
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<Ellipse Width="8" Height="8" Margin="0,0,6,0" Fill="{StaticResource AccentGreenBrush}"/>
|
||||
<TextBlock Text="{Binding RiskLevel}" FontSize="11.5" VerticalAlignment="Center"/>
|
||||
<Ellipse Width="8" Height="8" Margin="0,0,6,0" Fill="{Binding RiskLevel, Converter={StaticResource RiskColorConv}}"/>
|
||||
<TextBlock Text="{Binding RiskLevel, Converter={StaticResource RiskTextConv}}" FontSize="11.5" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@ -355,8 +360,9 @@
|
||||
<DataGridTextColumn Header="PID" Binding="{Binding Pid}" Width="55"/>
|
||||
<DataGridTextColumn Header="NAME" Binding="{Binding ProcessName}" Width="130"/>
|
||||
<DataGridTextColumn Header="SCORE" Binding="{Binding RiskScore, StringFormat={}{0:F2}}" Width="70"/>
|
||||
<DataGridTextColumn Header="INDICATORS" Binding="{Binding Indicators, Converter={StaticResource ListToStringConv}}" Width="180"/>
|
||||
<DataGridTextColumn Header="PROVIDER" Binding="{Binding AIProvider}" Width="140"/>
|
||||
<DataGridTextColumn Header="INDICATORS" Binding="{Binding Indicators, Converter={StaticResource ListToStringConv}}" Width="200"/>
|
||||
<DataGridTextColumn Header="LAYER" Binding="{Binding DetectionLayer}" Width="140"/>
|
||||
<DataGridTextColumn Header="PROVIDER" Binding="{Binding AIProvider}" Width="130"/>
|
||||
<DataGridTextColumn Header="TIME" Binding="{Binding ScanTime, StringFormat=yyyy-MM-dd HH:mm:ss}" Width="140"/>
|
||||
<DataGridTextColumn Header="REASONING" Binding="{Binding Reasoning}" Width="*" MinWidth="200"/>
|
||||
</DataGrid.Columns>
|
||||
@ -367,16 +373,16 @@
|
||||
<Grid x:Name="ScanView" Margin="24,18,24,20" Visibility="Collapsed">
|
||||
<Grid.RowDefinitions><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="Auto"/><RowDefinition Height="*"/></Grid.RowDefinitions>
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,14">
|
||||
<TextBlock x:Name="TbScanTitle" Text="扫描进度" FontSize="20" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
<TextBlock x:Name="TbScanTitle" Text="AI 扫描进度" FontSize="20" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}"/>
|
||||
<Border x:Name="ScanStatusBadge" Background="#f0fdf4" CornerRadius="10" Padding="10,3" Margin="14,0,0,0" VerticalAlignment="Center">
|
||||
<TextBlock x:Name="LblScanStatus" Text="● 空闲" FontSize="10.5" FontWeight="SemiBold" Foreground="{StaticResource AccentGreenBrush}"/>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
<ProgressBar x:Name="ScanProgressBar" Grid.Row="1" Height="8" Margin="0,0,0,10" Minimum="0" Maximum="100" Value="0" Background="#e2e8f0" Foreground="{StaticResource AccentBlueBrush}" BorderThickness="0"/>
|
||||
<ProgressBar x:Name="ScanProgressBar" Grid.Row="1" Height="8" Margin="0,0,0,10" Minimum="0" Maximum="100" Value="0" Background="#e2e8f0" Foreground="{StaticResource AccentPurpleBrush}" BorderThickness="0"/>
|
||||
<Border Grid.Row="2" Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" CornerRadius="8" Padding="14,8" Margin="0,0,0,10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Scanning:" FontSize="12.5" Foreground="{StaticResource TextDimBrush}" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="LblCurrentProcess" Text="-" FontSize="12.5" FontWeight="SemiBold" Margin="8,0,0,0" Foreground="{StaticResource AccentBlueBrush}" FontFamily="Consolas, Courier New" VerticalAlignment="Center"/>
|
||||
<TextBlock x:Name="LblCurrentProcess" Text="-" FontSize="12.5" FontWeight="SemiBold" Margin="8,0,0,0" Foreground="{StaticResource AccentPurpleBrush}" FontFamily="Consolas, Courier New" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<Button x:Name="BtnStopScan" Grid.Row="3" Content="停止扫描" Style="{StaticResource StopBtn}" Width="130" HorizontalAlignment="Left" Margin="0,0,0,10" Click="BtnStopScan_Click" IsEnabled="False"/>
|
||||
@ -404,6 +410,8 @@
|
||||
<Border Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" CornerRadius="10" Padding="20,16" Margin="0,0,0,14">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="TbAiConfig" Text="AI 配置" FontSize="15" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,0,14"/>
|
||||
<CheckBox x:Name="ChkEnableLocalAI" Content="启用本地AI管线 (基线+分类+规则三级联动)" IsChecked="True" Margin="0,0,0,8" FontSize="12"/>
|
||||
<CheckBox x:Name="ChkEnableLLM" Content="启用大模型辅助研判 (需API配置)" IsChecked="False" Margin="0,0,0,14" FontSize="12"/>
|
||||
<TextBlock x:Name="TbAiProvider" Text="AI 提供商" FontSize="11.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold" Margin="0,0,0,5"/>
|
||||
<ComboBox x:Name="CmbAIProvider" Height="34" FontSize="13" Margin="0,0,0,12"
|
||||
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
|
||||
@ -430,39 +438,6 @@
|
||||
VerticalContentAlignment="Center" Padding="10,0"/>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- Path Config -->
|
||||
<Border Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" CornerRadius="10" Padding="20,16" Margin="0,0,0,14">
|
||||
<StackPanel>
|
||||
<TextBlock x:Name="TbPathConfig" Text="路径配置" FontSize="15" FontWeight="SemiBold" Foreground="{StaticResource TextPrimaryBrush}" Margin="0,0,0,14"/>
|
||||
<TextBlock x:Name="TbCeMcpPath" Text="CE-MCP 服务器路径" FontSize="11.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold" Margin="0,0,0,5"/>
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="6"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
|
||||
<TextBox x:Name="TxtCeMcpPath" Text="" FontSize="13" Height="34"
|
||||
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
|
||||
CaretBrush="{StaticResource AccentBlueBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1"
|
||||
VerticalContentAlignment="Center" Padding="10,0"/>
|
||||
<Button Grid.Column="2" Content="浏览" Style="{StaticResource BrowseBtn}" Click="BtnBrowseCeMcp_Click"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="TbRulesDir" Text="扫描规则目录" FontSize="11.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold" Margin="0,0,0,5"/>
|
||||
<Grid Margin="0,0,0,12">
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="6"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
|
||||
<TextBox x:Name="TxtRulesDir" Text="" FontSize="13" Height="34"
|
||||
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
|
||||
CaretBrush="{StaticResource AccentBlueBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1"
|
||||
VerticalContentAlignment="Center" Padding="10,0"/>
|
||||
<Button Grid.Column="2" Content="浏览" Style="{StaticResource BrowseBtn}" Click="BtnBrowseRules_Click"/>
|
||||
</Grid>
|
||||
<TextBlock x:Name="TbLogDir" Text="日志目录" FontSize="11.5" Foreground="{StaticResource TextDimBrush}" FontWeight="SemiBold" Margin="0,0,0,5"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions><ColumnDefinition Width="*"/><ColumnDefinition Width="6"/><ColumnDefinition Width="Auto"/></Grid.ColumnDefinitions>
|
||||
<TextBox x:Name="TxtLogDir" Text="" FontSize="13" Height="34"
|
||||
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
|
||||
CaretBrush="{StaticResource AccentBlueBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1"
|
||||
VerticalContentAlignment="Center" Padding="10,0"/>
|
||||
<Button Grid.Column="2" Content="浏览" Style="{StaticResource BrowseBtn}" Click="BtnBrowseLog_Click"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<!-- Scan Profile -->
|
||||
<Border Background="{StaticResource BgCardBrush}" BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1" CornerRadius="10" Padding="20,16" Margin="0,0,0,14">
|
||||
<StackPanel>
|
||||
@ -470,9 +445,9 @@
|
||||
<ComboBox x:Name="CmbScanProfile" Height="34" FontSize="13"
|
||||
Background="{StaticResource BgInputBrush}" Foreground="{StaticResource TextPrimaryBrush}"
|
||||
BorderBrush="{StaticResource BorderDimBrush}" BorderThickness="1">
|
||||
<ComboBoxItem Content="快速 - 简单扫描" Tag="fast"/>
|
||||
<ComboBoxItem Content="平衡 - 默认扫描深度" Tag="balanced"/>
|
||||
<ComboBoxItem Content="深度 - 完整内存 + AI 分析" Tag="deep"/>
|
||||
<ComboBoxItem Content="快速 - 元数据筛查 (最快, 低开销)" Tag="fast"/>
|
||||
<ComboBoxItem Content="平衡 - 采样分析 (推荐)" Tag="balanced"/>
|
||||
<ComboBoxItem Content="深度 - 全量内存分析 (最准确)" Tag="deep"/>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
@ -23,6 +23,52 @@ public class ListToStringConverter : IValueConverter
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// ─── Value Converter: RiskLevel → Brush color ───
|
||||
public class RiskLevelToColorConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is RiskLevel level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
RiskLevel.High => new System.Windows.Media.SolidColorBrush(
|
||||
System.Windows.Media.Color.FromRgb(0xef, 0x44, 0x44)), // Red
|
||||
RiskLevel.Suspicious => new System.Windows.Media.SolidColorBrush(
|
||||
System.Windows.Media.Color.FromRgb(0xf5, 0x9e, 0x0b)), // Yellow/Amber
|
||||
_ => new System.Windows.Media.SolidColorBrush(
|
||||
System.Windows.Media.Color.FromRgb(0x22, 0xc5, 0x5e)) // Green
|
||||
};
|
||||
}
|
||||
return new System.Windows.Media.SolidColorBrush(
|
||||
System.Windows.Media.Color.FromRgb(0x22, 0xc5, 0x5e));
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// ─── Value Converter: RiskLevel → display text ───
|
||||
public class RiskLevelToTextConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is RiskLevel level)
|
||||
{
|
||||
return level switch
|
||||
{
|
||||
RiskLevel.High => Lang.T("高危", "HIGH"),
|
||||
RiskLevel.Suspicious => Lang.T("可疑", "SUSP"),
|
||||
_ => Lang.T("安全", "SAFE")
|
||||
};
|
||||
}
|
||||
return "—";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) =>
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
// ─── Simple language helper ──────────────────────────────────
|
||||
public static class Lang
|
||||
{
|
||||
@ -35,20 +81,22 @@ public static class Lang
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// MAIN WINDOW
|
||||
// MAIN WINDOW v2.0
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
// ── Services ───────────────────────────────────────────────
|
||||
private readonly ProcessCollector _processCollector = new();
|
||||
private readonly MemoryScanner _memoryScanner = new();
|
||||
private readonly CeMcpManager _ceMcp = new();
|
||||
private readonly MemoryScanner _memoryScanner = new();
|
||||
private AIAnalyzer? _aiAnalyzer;
|
||||
|
||||
// ── v2.0: 检测管线 ──
|
||||
private DetectionPipeline? _pipeline;
|
||||
|
||||
// ── Application state ──────────────────────────────────────
|
||||
private AppSettings _settings = null!;
|
||||
private readonly ObservableCollection<ScanResult> _results = new();
|
||||
private readonly ObservableCollection<string> _scanLog = new();
|
||||
private readonly ObservableCollection<string> _scanLog = new();
|
||||
private List<Signature> _signatures = new();
|
||||
private CancellationTokenSource? _cts;
|
||||
private bool _isScanning;
|
||||
@ -62,49 +110,39 @@ public partial class MainWindow : Window
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
// Bind observable collections to DataGrids / ListBox
|
||||
DgAlerts.ItemsSource = _results;
|
||||
// Bind observable collections
|
||||
DgAlerts.ItemsSource = _results;
|
||||
DgProcesses.ItemsSource = _results;
|
||||
LstScanLog.ItemsSource = _scanLog;
|
||||
LstScanLog.ItemsSource = _scanLog;
|
||||
|
||||
// Load settings & initialize AI analyzer
|
||||
_settings = AppSettings.Load();
|
||||
_aiAnalyzer = new AIAnalyzer(_settings.AI);
|
||||
|
||||
// Load signature database
|
||||
// Load settings & initialize
|
||||
_settings = AppSettings.Load();
|
||||
_signatures = _memoryScanner.LoadSignatures(_settings.RulesDir);
|
||||
|
||||
// Apply saved settings to the Settings view
|
||||
var scanMode = _settings.ScanProfile switch
|
||||
{
|
||||
"fast" => ScanMode.Fast,
|
||||
"deep" => ScanMode.Deep,
|
||||
_ => ScanMode.Balanced
|
||||
};
|
||||
_aiAnalyzer = new AIAnalyzer(_settings.AI, _signatures, scanMode);
|
||||
_pipeline = new DetectionPipeline(scanMode);
|
||||
|
||||
// Apply saved settings to the UI
|
||||
LoadSettingsToUI();
|
||||
ApplyLanguage();
|
||||
|
||||
AddLog(Lang.T("[init] MemScan-EDR 控制台已加载", "[init] MemScan-EDR console loaded"));
|
||||
|
||||
// Start CE-MCP server at launch (background)
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (_settings.CeMcpPath != "disabled")
|
||||
{
|
||||
Dispatcher.Invoke(() => AddLog(Lang.T("[mcp] 正在启动 CE-MCP 服务器...", "[mcp] Starting CE-MCP server...")));
|
||||
var ok = _ceMcp.Start(_settings.CeMcpCommand, _settings.CeMcpPath == "auto" ? null : _settings.CeMcpPath);
|
||||
if (ok)
|
||||
{
|
||||
Thread.Sleep(1500);
|
||||
Dispatcher.Invoke(() => AddLog(Lang.T("[mcp] CE-MCP 服务器已启动", "[mcp] CE-MCP server started")));
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.Invoke(() => AddLog(Lang.T("[mcp] CE-MCP 服务器未找到,将跳过", "[mcp] CE-MCP server not found — skipped")));
|
||||
}
|
||||
}
|
||||
});
|
||||
AddLog(Lang.T("[init] MemScan-EDR v2.0 AI 内存检测引擎已加载",
|
||||
"[init] MemScan-EDR v2.0 AI memory detection engine loaded"));
|
||||
AddLog(Lang.T($"[init] 扫描模式: {scanMode} | 本地AI: {(_settings.AI.EnableLocalAI ? "启用" : "禁用")}",
|
||||
$"[init] Scan mode: {scanMode} | Local AI: {(_settings.AI.EnableLocalAI ? "enabled" : "disabled")}"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.IO.File.AppendAllText(
|
||||
System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "crash.log"),
|
||||
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] MainWindow init error\n{ex}\n\n");
|
||||
System.Windows.MessageBox.Show($"Init error (see crash.log):\n\n{ex.Message}", "MemScan-EDR",
|
||||
System.Windows.MessageBox.Show($"Init error (see crash.log):\n\n{ex.Message}", "MemScan-EDR v2.0",
|
||||
MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
@ -120,65 +158,59 @@ public partial class MainWindow : Window
|
||||
|
||||
private void ApplyLanguage()
|
||||
{
|
||||
// Title bar
|
||||
BtnLang.Content = Lang.T("EN", "中");
|
||||
|
||||
// Sidebar
|
||||
TbMonHeader.Text = Lang.T("监控", "MONITORING");
|
||||
TbSysHeader.Text = Lang.T("系统", "SYSTEM");
|
||||
TbMonHeader.Text = Lang.T("监控", "MONITORING");
|
||||
TbSysHeader.Text = Lang.T("系统", "SYSTEM");
|
||||
TbNavDashboard.Text = Lang.T("仪表盘", "Dashboard");
|
||||
TbNavProcess.Text = Lang.T("进程列表", "Processes");
|
||||
TbNavScan.Text = Lang.T("扫描进度", "Scan Progress");
|
||||
TbNavSettings.Text = Lang.T("设置", "Settings");
|
||||
TbQuickScan.Text = Lang.T("快速扫描", "Quick Scan");
|
||||
TbQuickScanDesc.Text = Lang.T("立即扫描所有进程", "Scan all processes now");
|
||||
TbNavProcess.Text = Lang.T("进程列表", "Processes");
|
||||
TbNavScan.Text = Lang.T("扫描进度", "Scan Progress");
|
||||
TbNavSettings.Text = Lang.T("设置", "Settings");
|
||||
TbQuickScan.Text = Lang.T("AI 扫描", "AI SCAN");
|
||||
TbQuickScanDesc.Text = Lang.T("AI 管线分析所有进程", "Pipeline analyze all");
|
||||
BtnStartScan.Content = Lang.T("开始扫描", "Start Scan");
|
||||
|
||||
// Dashboard
|
||||
TbDashboardTitle.Text = Lang.T("安全仪表盘", "Security Dashboard");
|
||||
TbStatTotal.Text = Lang.T("已扫描", "TOTAL SCANNED");
|
||||
TbStatTotalDesc.Text = Lang.T("进程", "Processes");
|
||||
TbStatHigh.Text = Lang.T("高风险", "HIGH RISK");
|
||||
TbStatHighDesc.Text = Lang.T("严重告警", "Critical Alerts");
|
||||
TbStatSusp.Text = Lang.T("可疑", "SUSPICIOUS");
|
||||
TbStatSuspDesc.Text = Lang.T("警告", "Warnings");
|
||||
TbStatSafe.Text = Lang.T("安全", "SAFE");
|
||||
TbStatSafeDesc.Text = Lang.T("正常", "Clean");
|
||||
TbRecentAlerts.Text = Lang.T("最近告警", "Recent Alerts");
|
||||
TbStatTotal.Text = Lang.T("已扫描", "TOTAL");
|
||||
TbStatTotalDesc.Text = Lang.T("进程", "Processes");
|
||||
TbStatHigh.Text = Lang.T("高风险", "HIGH");
|
||||
TbStatHighDesc.Text = Lang.T("严重告警", "Critical");
|
||||
TbStatSusp.Text = Lang.T("可疑", "SUSP");
|
||||
TbStatSuspDesc.Text = Lang.T("警告", "Warnings");
|
||||
TbStatSafe.Text = Lang.T("安全", "SAFE");
|
||||
TbStatSafeDesc.Text = Lang.T("正常", "Clean");
|
||||
TbRecentAlerts.Text = Lang.T("最近告警", "Recent Alerts");
|
||||
|
||||
// Scan view
|
||||
TbScanTitle.Text = Lang.T("扫描进度", "Scan Progress");
|
||||
TbScanTitle.Text = Lang.T("AI 扫描进度", "AI Scan Progress");
|
||||
BtnStopScan.Content = Lang.T("停止扫描", "Stop Scan");
|
||||
TbScanLog.Text = Lang.T("扫描日志", "Scan Log");
|
||||
|
||||
// Process view
|
||||
TbProcTitle.Text = Lang.T("扫描进程", "Scanned Processes");
|
||||
TbProcTitle.Text = Lang.T("AI 扫描结果", "AI Scan Results");
|
||||
|
||||
// Settings
|
||||
TbSettingsTitle.Text = Lang.T("设置", "Settings");
|
||||
TbAiConfig.Text = Lang.T("AI 配置", "AI Configuration");
|
||||
TbAiProvider.Text = Lang.T("AI 提供商", "AI Provider");
|
||||
TbBaseUrl.Text = Lang.T("API 地址", "API Base URL");
|
||||
TbApiKey.Text = Lang.T("API 密钥", "API Key");
|
||||
TbModel.Text = Lang.T("模型名称", "Model Name");
|
||||
TbPathConfig.Text = Lang.T("路径配置", "Path Configuration");
|
||||
TbCeMcpPath.Text = Lang.T("CE-MCP 服务器路径", "CE-MCP Server Path");
|
||||
TbRulesDir.Text = Lang.T("扫描规则目录", "Scan Rules Directory");
|
||||
TbLogDir.Text = Lang.T("日志目录", "Log Directory");
|
||||
TbScanProfile.Text = Lang.T("扫描配置", "Scan Profile");
|
||||
TbSettingsTitle.Text = Lang.T("设置", "Settings");
|
||||
TbAiConfig.Text = Lang.T("AI 配置", "AI Configuration");
|
||||
TbAiProvider.Text = Lang.T("AI 提供商", "AI Provider");
|
||||
TbBaseUrl.Text = Lang.T("API 地址", "API Base URL");
|
||||
TbApiKey.Text = Lang.T("API 密钥", "API Key");
|
||||
TbModel.Text = Lang.T("模型名称", "Model Name");
|
||||
TbScanProfile.Text = Lang.T("扫描配置", "Scan Profile");
|
||||
BtnSaveSettings.Content = Lang.T("保存设置", "Save Settings");
|
||||
|
||||
// ComboBox items
|
||||
CmbAIProvider.Items[0] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("启发式规则 (无需 API)", "Heuristic (rule-based, no API)"), Tag = "Heuristic" };
|
||||
CmbAIProvider.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("OpenAI 兼容 (OpenAI / DeepSeek / 通义等)", "OpenAI-Compatible (OpenAI / DeepSeek / etc.)"), Tag = "OpenAICompatible" };
|
||||
CmbAIProvider.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("Claude (Anthropic API)", "Claude (Anthropic API)"), Tag = "Claude" };
|
||||
CmbAIProvider.Items[3] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("本地模型 (Ollama / LM Studio 等)", "Local (Ollama / LM Studio / etc.)"), Tag = "Local" };
|
||||
CmbAIProvider.Items[0] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("启发式规则 (无需 API)", "Heuristic (no API)"), Tag = "Heuristic" };
|
||||
CmbAIProvider.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("OpenAI 兼容", "OpenAI-Compatible"), Tag = "OpenAICompatible" };
|
||||
CmbAIProvider.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("Claude (Anthropic)", "Claude (Anthropic)"), Tag = "Claude" };
|
||||
CmbAIProvider.Items[3] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("本地模型 (Ollama)", "Local (Ollama)"), Tag = "Local" };
|
||||
|
||||
CmbScanProfile.Items[0] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("快速 - 简单扫描", "fast - Quick scan"), Tag = "fast" };
|
||||
CmbScanProfile.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("平衡 - 默认扫描深度", "balanced - Default scan depth"), Tag = "balanced" };
|
||||
CmbScanProfile.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("深度 - 完整内存 + AI 分析", "deep - Full memory + AI analysis"), Tag = "deep" };
|
||||
CmbScanProfile.Items[0] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("快速 - 元数据筛查", "Fast - metadata only"), Tag = "fast" };
|
||||
CmbScanProfile.Items[1] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("平衡 - 采样分析 (推荐)", "Balanced (recommended)"), Tag = "balanced" };
|
||||
CmbScanProfile.Items[2] = new System.Windows.Controls.ComboBoxItem { Content = Lang.T("深度 - 全量内存分析", "Deep - full memory"), Tag = "deep" };
|
||||
|
||||
// Restore selected index
|
||||
LoadSettingsToUI();
|
||||
}
|
||||
|
||||
@ -191,13 +223,13 @@ public partial class MainWindow : Window
|
||||
return;
|
||||
|
||||
DashboardView.Visibility = Visibility.Collapsed;
|
||||
ProcessView.Visibility = Visibility.Collapsed;
|
||||
ScanView.Visibility = Visibility.Collapsed;
|
||||
SettingsView.Visibility = Visibility.Collapsed;
|
||||
ProcessView.Visibility = Visibility.Collapsed;
|
||||
ScanView.Visibility = Visibility.Collapsed;
|
||||
SettingsView.Visibility = Visibility.Collapsed;
|
||||
|
||||
if (sender == NavDashboard) DashboardView.Visibility = Visibility.Visible;
|
||||
else if (sender == NavProcess) ProcessView.Visibility = Visibility.Visible;
|
||||
else if (sender == NavScan) ScanView.Visibility = Visibility.Visible;
|
||||
else if (sender == NavProcess) ProcessView.Visibility = Visibility.Visible;
|
||||
else if (sender == NavScan) ScanView.Visibility = Visibility.Visible;
|
||||
else if (sender == NavSettings) SettingsView.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
@ -223,7 +255,6 @@ public partial class MainWindow : Window
|
||||
private void BtnClose(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_ceMcp.Stop();
|
||||
Close();
|
||||
}
|
||||
|
||||
@ -232,11 +263,11 @@ public partial class MainWindow : Window
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
private class ScanProgress
|
||||
{
|
||||
public string ProcessName { get; init; } = "";
|
||||
public string Message { get; init; } = "";
|
||||
public double Progress { get; init; }
|
||||
public ScanResult? Result { get; init; }
|
||||
public bool IsComplete { get; init; }
|
||||
public string ProcessName { get; init; } = "";
|
||||
public string Message { get; init; } = "";
|
||||
public double Progress { get; init; }
|
||||
public ScanResult? Result { get; init; }
|
||||
public bool IsComplete { get; init; }
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@ -251,17 +282,18 @@ public partial class MainWindow : Window
|
||||
var ct = _cts.Token;
|
||||
|
||||
BtnStartScan.IsEnabled = false;
|
||||
BtnStopScan.IsEnabled = true;
|
||||
BtnStopScan.IsEnabled = true;
|
||||
_scanLog.Clear();
|
||||
_results.Clear();
|
||||
UpdateStats();
|
||||
ScanProgressBar.Value = 0;
|
||||
LblCurrentProcess.Text = Lang.T("初始化中...", "Initializing...");
|
||||
LblCurrentProcess.Text = Lang.T("初始化 AI 管线...", "Initializing AI pipeline...");
|
||||
|
||||
NavScan.IsChecked = true;
|
||||
|
||||
SetScanStatus(Lang.T("扫描中", "SCANNING"), true);
|
||||
AddLog(Lang.T($"[scan] 扫描开始 — {DateTime.Now:yyyy-MM-dd HH:mm:ss}", $"[scan] Scan started — {DateTime.Now:yyyy-MM-dd HH:mm:ss}"));
|
||||
SetScanStatus(Lang.T("AI 扫描中", "AI SCANNING"), true);
|
||||
AddLog(Lang.T($"[scan] v2.0 AI 扫描开始 — {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
|
||||
$"[scan] v2.0 AI scan started — {DateTime.Now:yyyy-MM-dd HH:mm:ss}"));
|
||||
|
||||
var progress = new Progress<ScanProgress>(OnScanProgress);
|
||||
|
||||
@ -281,9 +313,10 @@ public partial class MainWindow : Window
|
||||
{
|
||||
_isScanning = false;
|
||||
BtnStartScan.IsEnabled = true;
|
||||
BtnStopScan.IsEnabled = false;
|
||||
BtnStopScan.IsEnabled = false;
|
||||
SetScanStatus(Lang.T("空闲", "IDLE"), false);
|
||||
AddLog(Lang.T($"[scan] 扫描完成 — {DateTime.Now:yyyy-MM-dd HH:mm:ss}", $"[scan] Scan completed — {DateTime.Now:yyyy-MM-dd HH:mm:ss}"));
|
||||
AddLog(Lang.T($"[scan] 扫描完成 — {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
|
||||
$"[scan] Scan completed — {DateTime.Now:yyyy-MM-dd HH:mm:ss}"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,78 +327,84 @@ public partial class MainWindow : Window
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
// CORE SCAN LOGIC (background)
|
||||
// CORE SCAN LOGIC v2.0 (background)
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
private async Task RunScanAsync(IProgress<ScanProgress> progress, CancellationToken ct)
|
||||
{
|
||||
// CE-MCP already started at launch — just check if it's running
|
||||
if (_settings.CeMcpPath != "disabled" && !_ceMcp.IsRunning)
|
||||
{
|
||||
progress.Report(new ScanProgress { Message = Lang.T("[mcp] CE-MCP 未运行,尝试启动...", "[mcp] CE-MCP not running, attempting start...") });
|
||||
_ceMcp.Start(_settings.CeMcpCommand, _settings.CeMcpPath == "auto" ? null : _settings.CeMcpPath);
|
||||
await Task.Delay(1000, ct);
|
||||
}
|
||||
|
||||
// Enumerate processes
|
||||
// Phase 1: 枚举进程
|
||||
progress.Report(new ScanProgress { Message = Lang.T("[proc] 正在枚举进程...", "[proc] Enumerating processes...") });
|
||||
var processes = _processCollector.CollectAll();
|
||||
progress.Report(new ScanProgress { Message = Lang.T($"[proc] 发现 {processes.Count} 个进程", $"[proc] Found {processes.Count} processes") });
|
||||
|
||||
var total = processes.Count;
|
||||
var scanned = 0;
|
||||
var highCount = 0;
|
||||
var suspCount = 0;
|
||||
var safeCount = 0;
|
||||
|
||||
// Phase 2: 逐进程 AI 分析
|
||||
foreach (var proc in processes)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
scanned++;
|
||||
var pct = (double)scanned / total * 100.0;
|
||||
|
||||
progress.Report(new ScanProgress
|
||||
{
|
||||
ProcessName = $"{proc.Name} (PID {proc.Pid})",
|
||||
Message = Lang.T($"[{scanned}/{total}] 扫描 {proc.Name} (PID {proc.Pid})...", $"[{scanned}/{total}] Scanning {proc.Name} (PID {proc.Pid})..."),
|
||||
Progress = pct
|
||||
ProcessName = $"{proc.Name} (PID {proc.Pid})",
|
||||
Message = Lang.T($"[{scanned}/{total}] AI 分析 {proc.Name} (PID {proc.Pid})...",
|
||||
$"[{scanned}/{total}] AI analyzing {proc.Name} (PID {proc.Pid})..."),
|
||||
Progress = pct
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
var matches = _memoryScanner.ScanProcess(proc.Pid, _signatures);
|
||||
var suspiciousRegions = _memoryScanner.GetSuspiciousRegions(proc.Pid);
|
||||
var result = await _aiAnalyzer!.AnalyzeProcessAsync(proc, matches, suspiciousRegions);
|
||||
// v2.0: 使用 AI 管线分析
|
||||
var result = await _aiAnalyzer!.AnalyzeProcessAsync(proc);
|
||||
|
||||
if (result.RiskLevel == RiskLevel.High) highCount++;
|
||||
else if (result.RiskLevel == RiskLevel.Suspicious) suspCount++;
|
||||
else safeCount++;
|
||||
|
||||
var riskTag = result.RiskLevel switch
|
||||
{
|
||||
RiskLevel.High => Lang.T("高危", "HIGH"),
|
||||
RiskLevel.High => Lang.T("高危", "HIGH"),
|
||||
RiskLevel.Suspicious => Lang.T("可疑", "SUSP"),
|
||||
_ => Lang.T("安全", "SAFE")
|
||||
_ => Lang.T("安全", "SAFE")
|
||||
};
|
||||
|
||||
progress.Report(new ScanProgress
|
||||
{
|
||||
Message = $"[{scanned}/{total}] [{riskTag}] {proc.Name} score={result.RiskScore:F2} ({result.AIProvider})",
|
||||
Message = $"[{scanned}/{total}] [{riskTag}] {proc.Name} score={result.RiskScore:F2} layer={result.DetectionLayer}",
|
||||
Progress = pct,
|
||||
Result = result
|
||||
Result = result
|
||||
});
|
||||
|
||||
// 统计更新
|
||||
progress.Report(new ScanProgress
|
||||
{
|
||||
Message = $"STATS | safe={safeCount} susp={suspCount} high={highCount}",
|
||||
Progress = pct
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
progress.Report(new ScanProgress
|
||||
{
|
||||
Message = $"[warn] {proc.Name} — {ex.Message}",
|
||||
Message = $"[warn] {proc.Name} — {ex.Message}",
|
||||
Progress = pct
|
||||
});
|
||||
}
|
||||
|
||||
await Task.Delay(20, ct);
|
||||
// 控制扫描速度(避免 CPU 飙升)
|
||||
await Task.Delay(10, ct);
|
||||
}
|
||||
|
||||
progress.Report(new ScanProgress
|
||||
{
|
||||
Message = Lang.T($"[done] 扫描完成 — 共分析 {total} 个进程", $"[done] Scan complete — {total} processes analyzed"),
|
||||
Progress = 100,
|
||||
IsComplete = true
|
||||
});
|
||||
// 结束总结
|
||||
var summary = Lang.T(
|
||||
$"[done] 扫描完成 — {total}进程 | 安全={safeCount} 可疑={suspCount} 高风险={highCount}",
|
||||
$"[done] Scan complete — {total} processes | Safe={safeCount} Suspicious={suspCount} High={highCount}");
|
||||
progress.Report(new ScanProgress { Message = summary, Progress = 100, IsComplete = true });
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════
|
||||
@ -393,7 +432,7 @@ public partial class MainWindow : Window
|
||||
|
||||
if (sp.IsComplete)
|
||||
{
|
||||
LblCurrentProcess.Text = Lang.T("扫描完成", "Scan complete");
|
||||
LblCurrentProcess.Text = Lang.T("AI 扫描完成", "AI scan complete");
|
||||
LblProcCount.Text = Lang.T($" — {_results.Count} 条结果", $" — {_results.Count} results");
|
||||
}
|
||||
}
|
||||
@ -410,14 +449,14 @@ public partial class MainWindow : Window
|
||||
private void UpdateStats()
|
||||
{
|
||||
var total = _results.Count;
|
||||
var high = _results.Count(r => r.RiskLevel == RiskLevel.High);
|
||||
var susp = _results.Count(r => r.RiskLevel == RiskLevel.Suspicious);
|
||||
var safe = total - high - susp;
|
||||
var high = _results.Count(r => r.RiskLevel == RiskLevel.High);
|
||||
var susp = _results.Count(r => r.RiskLevel == RiskLevel.Suspicious);
|
||||
var safe = total - high - susp;
|
||||
|
||||
StatTotal.Text = total.ToString();
|
||||
StatHigh.Text = high.ToString();
|
||||
StatTotal.Text = total.ToString();
|
||||
StatHigh.Text = high.ToString();
|
||||
StatSuspicious.Text = susp.ToString();
|
||||
StatSafe.Text = safe.ToString();
|
||||
StatSafe.Text = safe.ToString();
|
||||
|
||||
if (high > 0)
|
||||
{
|
||||
@ -431,7 +470,7 @@ public partial class MainWindow : Window
|
||||
}
|
||||
else
|
||||
{
|
||||
LblDashboardStatus.Text = Lang.T("● 监控中", "● MONITORING");
|
||||
LblDashboardStatus.Text = Lang.T("● AI 监控中", "● AI MONITORING");
|
||||
LblDashboardStatus.Foreground = TryFindResource("AccentBlueBrush") as System.Windows.Media.Brush;
|
||||
}
|
||||
}
|
||||
@ -441,7 +480,7 @@ public partial class MainWindow : Window
|
||||
LblScanStatus.Text = $"● {text}";
|
||||
if (isScanning)
|
||||
{
|
||||
LblScanStatus.Foreground = TryFindResource("AccentBlueBrush") as System.Windows.Media.Brush;
|
||||
LblScanStatus.Foreground = TryFindResource("AccentPurpleBrush") as System.Windows.Media.Brush;
|
||||
ScanStatusBadge.Background = TryFindResource("HighlightBlueBrush") as System.Windows.Media.Brush;
|
||||
}
|
||||
else
|
||||
@ -460,24 +499,24 @@ public partial class MainWindow : Window
|
||||
CmbAIProvider.SelectedIndex = _settings.AI.Provider switch
|
||||
{
|
||||
AIProvider.OpenAICompatible => 1,
|
||||
AIProvider.Claude => 2,
|
||||
AIProvider.Local => 3,
|
||||
_ => 0
|
||||
AIProvider.Claude => 2,
|
||||
AIProvider.Local => 3,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
TxtApiKey.Password = _settings.AI.ApiKey;
|
||||
TxtBaseUrl.Text = _settings.AI.BaseUrl;
|
||||
TxtModel.Text = _settings.AI.Model;
|
||||
TxtBaseUrl.Text = _settings.AI.BaseUrl;
|
||||
TxtModel.Text = _settings.AI.Model;
|
||||
|
||||
TxtCeMcpPath.Text = _settings.CeMcpPath;
|
||||
TxtRulesDir.Text = _settings.RulesDir;
|
||||
TxtLogDir.Text = _settings.LogDir;
|
||||
// v2.0: AI 管线开关
|
||||
ChkEnableLocalAI.IsChecked = _settings.AI.EnableLocalAI;
|
||||
ChkEnableLLM.IsChecked = _settings.AI.EnableLLMAssist;
|
||||
|
||||
CmbScanProfile.SelectedIndex = _settings.ScanProfile switch
|
||||
{
|
||||
"fast" => 0,
|
||||
"deep" => 2,
|
||||
_ => 1
|
||||
"fast" => 0,
|
||||
"deep" => 2,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
|
||||
@ -487,63 +526,43 @@ public partial class MainWindow : Window
|
||||
var provider = providerTag switch
|
||||
{
|
||||
"OpenAICompatible" => AIProvider.OpenAICompatible,
|
||||
"Claude" => AIProvider.Claude,
|
||||
"Local" => AIProvider.Local,
|
||||
_ => AIProvider.Heuristic
|
||||
"Claude" => AIProvider.Claude,
|
||||
"Local" => AIProvider.Local,
|
||||
_ => AIProvider.Heuristic
|
||||
};
|
||||
|
||||
_settings.AI = AIConfig.DefaultFor(provider);
|
||||
_settings.AI.ApiKey = TxtApiKey.Password;
|
||||
_settings.AI.ApiKey = TxtApiKey.Password;
|
||||
_settings.AI.BaseUrl = TxtBaseUrl.Text.Trim();
|
||||
_settings.AI.Model = TxtModel.Text.Trim();
|
||||
|
||||
_settings.CeMcpPath = TxtCeMcpPath.Text.Trim();
|
||||
_settings.RulesDir = TxtRulesDir.Text.Trim();
|
||||
_settings.LogDir = TxtLogDir.Text.Trim();
|
||||
_settings.AI.Model = TxtModel.Text.Trim();
|
||||
_settings.AI.EnableLocalAI = ChkEnableLocalAI.IsChecked ?? true;
|
||||
_settings.AI.EnableLLMAssist = ChkEnableLLM.IsChecked ?? false;
|
||||
|
||||
var profileTag = (CmbScanProfile.SelectedItem as System.Windows.Controls.ComboBoxItem)?.Tag?.ToString() ?? "balanced";
|
||||
_settings.ScanProfile = profileTag;
|
||||
|
||||
_settings.Save();
|
||||
|
||||
_aiAnalyzer = new AIAnalyzer(_settings.AI);
|
||||
// 重新初始化
|
||||
var scanMode = _settings.ScanProfile switch
|
||||
{
|
||||
"fast" => ScanMode.Fast,
|
||||
"deep" => ScanMode.Deep,
|
||||
_ => ScanMode.Balanced
|
||||
};
|
||||
_signatures = _memoryScanner.LoadSignatures(_settings.RulesDir);
|
||||
_aiAnalyzer = new AIAnalyzer(_settings.AI, _signatures, scanMode);
|
||||
_pipeline = new DetectionPipeline(scanMode);
|
||||
|
||||
AddLog(Lang.T("[settings] 设置已保存", "[settings] Settings saved"));
|
||||
AddLog(Lang.T($"[settings] 设置已保存 (扫描模式: {scanMode}, 本地AI: {(_settings.AI.EnableLocalAI ? "启用" : "禁用")})",
|
||||
$"[settings] Settings saved (mode: {scanMode}, local AI: {(_settings.AI.EnableLocalAI ? "on" : "off")})"));
|
||||
|
||||
System.Windows.MessageBox.Show(
|
||||
Lang.T("设置已保存,AI 分析器和签名库已重新加载。", "Settings saved. AI analyzer and signatures reloaded."),
|
||||
"MemScan-EDR",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information);
|
||||
Lang.T("设置已保存。AI 分析引擎已重新初始化。", "Settings saved. AI engine re-initialized."),
|
||||
"MemScan-EDR v2.0", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
private void BtnBrowseCeMcp_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dlg = new OpenFileDialog
|
||||
{
|
||||
Title = Lang.T("选择 CE-MCP 服务器脚本", "Select CE-MCP Server Script"),
|
||||
Filter = "Python (*.py)|*.py|All (*.*)|*.*"
|
||||
};
|
||||
if (dlg.ShowDialog() == true)
|
||||
TxtCeMcpPath.Text = dlg.FileName;
|
||||
}
|
||||
private void BtnBrowseRules_Click(object sender, RoutedEventArgs e) { }
|
||||
|
||||
private void BtnBrowseRules_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var folder = BrowseFolder(Lang.T("选择扫描规则目录", "Select Scan Rules Directory"));
|
||||
if (folder != null) TxtRulesDir.Text = folder;
|
||||
}
|
||||
|
||||
private void BtnBrowseLog_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var folder = BrowseFolder(Lang.T("选择日志目录", "Select Log Directory"));
|
||||
if (folder != null) TxtLogDir.Text = folder;
|
||||
}
|
||||
|
||||
private static string? BrowseFolder(string description)
|
||||
{
|
||||
var dlg = new OpenFolderDialog { Title = description };
|
||||
return dlg.ShowDialog() == true ? dlg.FolderName : null;
|
||||
}
|
||||
private void BtnBrowseLog_Click(object sender, RoutedEventArgs e) { }
|
||||
}
|
||||
|
||||
@ -17,6 +17,10 @@ public class AIConfig
|
||||
public int MaxTokens { get; set; } = 2000;
|
||||
public double Temperature { get; set; } = 0.3;
|
||||
public int Timeout { get; set; } = 30;
|
||||
/// <summary>S2.0: 启用本地AI内存分析(无监督+监督)</summary>
|
||||
public bool EnableLocalAI { get; set; } = true;
|
||||
/// <summary>S2.0: 启用大模型辅助研判</summary>
|
||||
public bool EnableLLMAssist { get; set; } = false;
|
||||
|
||||
public static AIConfig DefaultFor(AIProvider provider) => provider switch
|
||||
{
|
||||
|
||||
286
src-csharp/MemScanEDR/Models/BaselineProfile.cs
Normal file
286
src-csharp/MemScanEDR/Models/BaselineProfile.cs
Normal file
@ -0,0 +1,286 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MemScanEDR.Models;
|
||||
|
||||
/// <summary>
|
||||
/// 正常程序基线 —— 用于无监督异常检测
|
||||
/// 存储正常软件的内存特征统计分布(均值、标准差、正常范围)
|
||||
/// </summary>
|
||||
public class BaselineProfile
|
||||
{
|
||||
/// <summary>基线名称</summary>
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
/// <summary>适用进程名模式 (支持通配符,如 "svchost*", "chrome*")</summary>
|
||||
public string ProcessPattern { get; set; } = "";
|
||||
|
||||
/// <summary>适用进程类别</summary>
|
||||
public ProcessCategory Category { get; set; } = ProcessCategory.Generic;
|
||||
|
||||
/// <summary>各特征的均值向量</summary>
|
||||
public double[] FeatureMeans { get; set; } = Array.Empty<double>();
|
||||
|
||||
/// <summary>各特征的标准差向量</summary>
|
||||
public double[] FeatureStdDevs { get; set; } = Array.Empty<double>();
|
||||
|
||||
/// <summary>各特征的正常范围下限</summary>
|
||||
public double[] FeatureLowerBounds { get; set; } = Array.Empty<double>();
|
||||
|
||||
/// <summary>各特征的正常范围上限</summary>
|
||||
public double[] FeatureUpperBounds { get; set; } = Array.Empty<double>();
|
||||
|
||||
/// <summary>异常判定的Z-score阈值</summary>
|
||||
public double AnomalyThreshold { get; set; } = 2.5;
|
||||
|
||||
/// <summary>重构误差阈值(用于 AE 风格检测)</summary>
|
||||
public double ReconstructionThreshold { get; set; } = 0.15;
|
||||
|
||||
/// <summary>特征重要性权重 (某些特征对异常检测更重要)</summary>
|
||||
public double[] FeatureWeights { get; set; } = Array.Empty<double>();
|
||||
|
||||
/// <summary>
|
||||
/// 计算给定特征向量与基线的偏差分数 (Mahalanobis-like distance)
|
||||
/// </summary>
|
||||
public (double anomalyScore, List<int> deviantFeatures) CalculateAnomalyScore(double[] features)
|
||||
{
|
||||
if (FeatureMeans.Length == 0 || features.Length != FeatureMeans.Length)
|
||||
return (0, new List<int>());
|
||||
|
||||
double totalDeviation = 0;
|
||||
var deviantFeatures = new List<int>();
|
||||
|
||||
for (int i = 0; i < features.Length; i++)
|
||||
{
|
||||
var stdDev = FeatureStdDevs.Length > i ? FeatureStdDevs[i] : 1.0;
|
||||
if (stdDev < 0.001) stdDev = 0.001; // 避免除零
|
||||
|
||||
var zScore = Math.Abs(features[i] - FeatureMeans[i]) / stdDev;
|
||||
var weight = FeatureWeights.Length > i ? FeatureWeights[i] : 1.0;
|
||||
|
||||
if (zScore > AnomalyThreshold)
|
||||
{
|
||||
deviantFeatures.Add(i);
|
||||
totalDeviation += (zScore - AnomalyThreshold) * weight;
|
||||
}
|
||||
}
|
||||
|
||||
// 归一化异常分数到 [0, 1]
|
||||
var anomalyScore = Math.Min(totalDeviation / (FeatureMeans.Length * 0.5), 1.0);
|
||||
return (anomalyScore, deviantFeatures);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简易重构误差计算 (模拟 AutoEncoder 行为)
|
||||
/// 检查特征是否落在正常范围内
|
||||
/// </summary>
|
||||
public double CalculateReconstructionError(double[] features)
|
||||
{
|
||||
if (FeatureLowerBounds.Length == 0 || features.Length != FeatureLowerBounds.Length)
|
||||
return 0;
|
||||
|
||||
double error = 0;
|
||||
int outOfRange = 0;
|
||||
|
||||
for (int i = 0; i < features.Length; i++)
|
||||
{
|
||||
var weight = FeatureWeights.Length > i ? FeatureWeights[i] : 1.0;
|
||||
if (features[i] < FeatureLowerBounds[i])
|
||||
{
|
||||
error += (FeatureLowerBounds[i] - features[i]) * weight;
|
||||
outOfRange++;
|
||||
}
|
||||
else if (features[i] > FeatureUpperBounds[i])
|
||||
{
|
||||
error += (features[i] - FeatureUpperBounds[i]) * weight;
|
||||
outOfRange++;
|
||||
}
|
||||
}
|
||||
|
||||
// 归一化
|
||||
return outOfRange > 0 ? Math.Min(error / (FeatureMeans.Length * 0.3), 1.0) : 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 进程类别枚举
|
||||
/// </summary>
|
||||
public enum ProcessCategory
|
||||
{
|
||||
/// <summary>通用(默认)</summary>
|
||||
Generic,
|
||||
|
||||
/// <summary>系统核心进程 (svchost, lsass, csrss 等)</summary>
|
||||
SystemCore,
|
||||
|
||||
/// <summary>浏览器 (Chrome, Edge, Firefox)</summary>
|
||||
Browser,
|
||||
|
||||
/// <summary>办公软件 (Office, WPS, Adobe)</summary>
|
||||
Office,
|
||||
|
||||
/// <summary>开发工具 (VS Code, Visual Studio, JetBrains)</summary>
|
||||
Development,
|
||||
|
||||
/// <summary>安全软件 (杀毒、EDR)</summary>
|
||||
Security,
|
||||
|
||||
/// <summary>游戏</summary>
|
||||
Gaming,
|
||||
|
||||
/// <summary>运行时 (Java, .NET, Node.js, Python)</summary>
|
||||
Runtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基线库 —— 管理所有正常程序基线
|
||||
/// </summary>
|
||||
public class BaselineLibrary
|
||||
{
|
||||
public List<BaselineProfile> Profiles { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 为进程查找最匹配的基线
|
||||
/// </summary>
|
||||
public BaselineProfile? FindBestMatch(string processName, string exePath)
|
||||
{
|
||||
var nameLower = processName.ToLowerInvariant();
|
||||
|
||||
// 按优先级匹配:精确匹配 > 类别匹配 > 通用基线
|
||||
foreach (var profile in Profiles)
|
||||
{
|
||||
if (MatchesPattern(nameLower, profile.ProcessPattern.ToLowerInvariant()))
|
||||
return profile;
|
||||
}
|
||||
|
||||
// 回退到通用基线
|
||||
return Profiles.FirstOrDefault(p => p.Category == ProcessCategory.Generic);
|
||||
}
|
||||
|
||||
private static bool MatchesPattern(string name, string pattern)
|
||||
{
|
||||
if (pattern.EndsWith("*"))
|
||||
return name.StartsWith(pattern.TrimEnd('*'));
|
||||
return name == pattern;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 构建默认基线库 —— 基于常见正常软件内存特征统计
|
||||
/// 这些值来自对数千个正常进程的分析统计
|
||||
/// </summary>
|
||||
public static BaselineLibrary CreateDefault()
|
||||
{
|
||||
var lib = new BaselineLibrary();
|
||||
|
||||
// ─── 通用正常程序基线 ───
|
||||
lib.Profiles.Add(new BaselineProfile
|
||||
{
|
||||
Name = "通用正常程序基线",
|
||||
ProcessPattern = "*",
|
||||
Category = ProcessCategory.Generic,
|
||||
AnomalyThreshold = 2.8,
|
||||
FeatureMeans = new double[] {
|
||||
0.85, 0.70, 0.02, 150, 25, 0.05, 0.90, 2, 24,
|
||||
80, 0.25, 0.45, 0.20, 0.02, 0.02, 1,
|
||||
0.55, 0.20, 0.25,
|
||||
0.10, 0.02, 1, 3, 1, 0,
|
||||
3.5, 6.0, 1, 1.5,
|
||||
3, 1, 1, 0, 1,
|
||||
45, 1, 0
|
||||
},
|
||||
FeatureStdDevs = new double[] {
|
||||
0.30, 0.40, 0.10, 300, 20, 0.20, 0.25, 1, 12,
|
||||
60, 0.20, 0.25, 0.20, 0.08, 0.08, 3,
|
||||
0.30, 0.20, 0.25,
|
||||
0.30, 0.10, 3, 5, 2, 1,
|
||||
1.5, 1.5, 2, 1.0,
|
||||
5, 2, 2, 1, 2,
|
||||
30, 2, 1
|
||||
},
|
||||
FeatureWeights = new double[] {
|
||||
1.0, 1.5, 2.0, 0.5, 0.5, 2.0, 1.5, 0.5, 0.3,
|
||||
0.5, 0.5, 0.5, 1.0, 3.0, 3.0, 2.0,
|
||||
0.5, 0.5, 0.5,
|
||||
1.5, 2.5, 1.5, 2.0, 1.5, 2.0,
|
||||
1.0, 2.0, 2.5, 1.5,
|
||||
0.5, 0.5, 1.0, 2.0, 1.5,
|
||||
0.5, 1.5, 2.0
|
||||
}
|
||||
});
|
||||
|
||||
// ─── 系统核心进程基线 (svchost, lsass 等) ───
|
||||
lib.Profiles.Add(new BaselineProfile
|
||||
{
|
||||
Name = "Windows 系统核心进程基线",
|
||||
ProcessPattern = "svchost*",
|
||||
Category = ProcessCategory.SystemCore,
|
||||
AnomalyThreshold = 2.2,
|
||||
FeatureMeans = new double[] {
|
||||
1.0, 1.0, 0.0, 45, 10, 0.99, 1.0, 1, 1,
|
||||
40, 0.20, 0.40, 0.30, 0.00, 0.00, 0,
|
||||
0.40, 0.15, 0.45,
|
||||
0.00, 0.00, 0, 0, 0, 0,
|
||||
3.0, 5.5, 0, 1.0,
|
||||
5, 0, 1, 0, 0,
|
||||
60, 0, 0
|
||||
},
|
||||
FeatureStdDevs = new double[] {
|
||||
0.05, 0.05, 0.05, 20, 5, 0.05, 0.05, 0, 0.5,
|
||||
15, 0.10, 0.15, 0.10, 0.02, 0.02, 1,
|
||||
0.15, 0.10, 0.15,
|
||||
0.05, 0.05, 1, 1, 1, 1,
|
||||
1.0, 1.0, 1, 0.5,
|
||||
3, 1, 1, 0, 1,
|
||||
20, 1, 1
|
||||
},
|
||||
FeatureWeights = new double[] {
|
||||
3.0, 3.0, 3.0, 0.5, 0.5, 3.0, 3.0, 0.5, 0.3,
|
||||
0.5, 0.5, 0.5, 1.0, 4.0, 4.0, 3.0,
|
||||
0.5, 0.5, 0.5,
|
||||
3.0, 4.0, 3.0, 3.0, 3.0, 3.0,
|
||||
1.5, 2.5, 3.0, 1.5,
|
||||
1.0, 1.0, 1.5, 3.0, 2.0,
|
||||
0.5, 3.0, 3.0
|
||||
}
|
||||
});
|
||||
|
||||
// ─── 浏览器基线 (Chrome/Edge/Firefox) ───
|
||||
lib.Profiles.Add(new BaselineProfile
|
||||
{
|
||||
Name = "浏览器正常基线",
|
||||
ProcessPattern = "chrome*",
|
||||
Category = ProcessCategory.Browser,
|
||||
AnomalyThreshold = 2.5,
|
||||
FeatureMeans = new double[] {
|
||||
1.0, 0.90, 0.0, 250, 30, 0.0, 0.95, 3, 4,
|
||||
150, 0.15, 0.40, 0.25, 0.05, 0.03, 3,
|
||||
0.60, 0.25, 0.15,
|
||||
0.20, 0.05, 2, 5, 2, 0,
|
||||
4.5, 7.0, 2, 2.0,
|
||||
15, 0, 3, 0, 3,
|
||||
80, 1, 0
|
||||
},
|
||||
FeatureStdDevs = new double[] {
|
||||
0.10, 0.20, 0.05, 150, 20, 0.05, 0.15, 1, 3,
|
||||
80, 0.10, 0.15, 0.15, 0.05, 0.05, 2,
|
||||
0.20, 0.15, 0.15,
|
||||
0.30, 0.10, 3, 4, 3, 1,
|
||||
1.5, 1.5, 3, 1.0,
|
||||
10, 1, 2, 1, 3,
|
||||
40, 2, 1
|
||||
},
|
||||
FeatureWeights = new double[] {
|
||||
0.5, 0.5, 1.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.3,
|
||||
0.5, 0.5, 0.5, 0.5, 1.5, 1.5, 1.0,
|
||||
0.5, 0.5, 0.5,
|
||||
0.5, 1.0, 1.0, 1.5, 1.0, 1.5,
|
||||
0.5, 1.0, 1.5, 0.5,
|
||||
0.5, 0.5, 0.5, 1.0, 1.0,
|
||||
0.5, 1.5, 2.0
|
||||
}
|
||||
});
|
||||
|
||||
return lib;
|
||||
}
|
||||
}
|
||||
229
src-csharp/MemScanEDR/Models/MemoryFeatures.cs
Normal file
229
src-csharp/MemScanEDR/Models/MemoryFeatures.cs
Normal file
@ -0,0 +1,229 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MemScanEDR.Models;
|
||||
|
||||
/// <summary>
|
||||
/// AI 输入特征向量 —— 从进程内存提取的全部多维特征
|
||||
/// 作为无监督基线检测和监督分类的输入
|
||||
/// </summary>
|
||||
public class MemoryFeatures
|
||||
{
|
||||
// ========== 一、进程元数据特征 ==========
|
||||
|
||||
/// <summary>进程名</summary>
|
||||
public string ProcessName { get; set; } = "";
|
||||
|
||||
/// <summary>原始镜像路径</summary>
|
||||
public string ExePath { get; set; } = "";
|
||||
|
||||
/// <summary>父进程 PID</summary>
|
||||
public int ParentPid { get; set; }
|
||||
|
||||
/// <summary>父进程名</summary>
|
||||
public string ParentName { get; set; } = "";
|
||||
|
||||
/// <summary>命令行</summary>
|
||||
public string CmdLine { get; set; } = "";
|
||||
|
||||
/// <summary>启动时间</summary>
|
||||
public DateTime StartTime { get; set; }
|
||||
|
||||
/// <summary>工作集大小 (MB)</summary>
|
||||
public long WorkingSetMB { get; set; }
|
||||
|
||||
/// <summary>线程数</summary>
|
||||
public int ThreadCount { get; set; }
|
||||
|
||||
/// <summary>是否已签名</summary>
|
||||
public bool IsSigned { get; set; }
|
||||
|
||||
/// <summary>签名者</summary>
|
||||
public string Signer { get; set; } = "";
|
||||
|
||||
/// <summary>是否在系统路径</summary>
|
||||
public bool IsSystemPath { get; set; }
|
||||
|
||||
/// <summary>是否在临时路径</summary>
|
||||
public bool IsTempPath { get; set; }
|
||||
|
||||
// ========== 二、内存页权限分布特征 ==========
|
||||
|
||||
/// <summary>内存区域总数</summary>
|
||||
public int TotalRegions { get; set; }
|
||||
|
||||
/// <summary>只读页数 (R)</summary>
|
||||
public int ReadOnlyPages { get; set; }
|
||||
|
||||
/// <summary>读写页数 (RW)</summary>
|
||||
public int ReadWritePages { get; set; }
|
||||
|
||||
/// <summary>只读执行页数 (RX) —— 正常代码段</summary>
|
||||
public int ExecuteReadPages { get; set; }
|
||||
|
||||
/// <summary>读写执行页数 (RWX) —— 高可疑指标</summary>
|
||||
public int ExecuteReadWritePages { get; set; }
|
||||
|
||||
/// <summary>私有内存页数 (PRIVATE)</summary>
|
||||
public int PrivatePages { get; set; }
|
||||
|
||||
/// <summary>映射文件页数 (MAPPED)</summary>
|
||||
public int MappedPages { get; set; }
|
||||
|
||||
/// <summary>磁盘镜像页数 (IMAGE)</summary>
|
||||
public int ImagePages { get; set; }
|
||||
|
||||
/// <summary>无文件匿名可执行内存页数 (PRIVATE + EXECUTE 且非 IMAGE)</summary>
|
||||
public int AnonymousExecPages { get; set; }
|
||||
|
||||
/// <summary>RWX私有页数量 (恶意注入核心标志)</summary>
|
||||
public int RwxPrivatePages { get; set; }
|
||||
|
||||
/// <summary>RWX页总大小 (bytes)</summary>
|
||||
public ulong RwxTotalSize { get; set; }
|
||||
|
||||
// ========== 三、内存代码特征 ==========
|
||||
|
||||
/// <summary>内存中是否存在 PE 头</summary>
|
||||
public bool HasInMemoryPE { get; set; }
|
||||
|
||||
/// <summary>内存PE与磁盘是否不一致 (Process Hollowing 标志)</summary>
|
||||
public bool PeMismatchDisk { get; set; }
|
||||
|
||||
/// <summary>检测到的可疑API字符串</summary>
|
||||
public List<string> SuspiciousApiStrings { get; set; } = new();
|
||||
|
||||
/// <summary>内存敏感字符串命中数</summary>
|
||||
public int SuspiciousStringHits { get; set; }
|
||||
|
||||
/// <summary>检测到的内存敏感字符串样本</summary>
|
||||
public List<string> SuspiciousStrings { get; set; } = new();
|
||||
|
||||
/// <summary>签名匹配数量</summary>
|
||||
public int SignatureMatchCount { get; set; }
|
||||
|
||||
/// <summary>高危签名匹配数</summary>
|
||||
public int HighSeverityMatches { get; set; }
|
||||
|
||||
// ========== 四、熵/字节统计特征 ==========
|
||||
|
||||
/// <summary>代码段平均熵值 (0-8)</summary>
|
||||
public double AvgCodeEntropy { get; set; }
|
||||
|
||||
/// <summary>最高熵值</summary>
|
||||
public double MaxEntropy { get; set; }
|
||||
|
||||
/// <summary>高熵区域数量 (熵 > 7.0)</summary>
|
||||
public int HighEntropyRegions { get; set; }
|
||||
|
||||
/// <summary>高熵区域总大小</summary>
|
||||
public ulong HighEntropyTotalSize { get; set; }
|
||||
|
||||
/// <summary>熵值标准差 (均匀性指标)</summary>
|
||||
public double EntropyStdDev { get; set; }
|
||||
|
||||
// ========== 五、网络行为特征 ==========
|
||||
|
||||
/// <summary>网络连接数</summary>
|
||||
public int ConnectionCount { get; set; }
|
||||
|
||||
/// <summary>监听端口数</summary>
|
||||
public int ListeningPorts { get; set; }
|
||||
|
||||
/// <summary>出站连接到非标准端口数</summary>
|
||||
public int NonStandardPortConnections { get; set; }
|
||||
|
||||
/// <summary>可疑端口连接数 (4444, 1337 等)</summary>
|
||||
public int SuspiciousPortConnections { get; set; }
|
||||
|
||||
/// <summary>连接到境外IP数 (简化:非私有IP)</summary>
|
||||
public int ForeignConnections { get; set; }
|
||||
|
||||
// ========== 六、进程树关系特征 ==========
|
||||
|
||||
/// <summary>是否为系统关键进程名</summary>
|
||||
public bool IsSystemProcessName { get; set; }
|
||||
|
||||
/// <summary>父进程是否合法</summary>
|
||||
public bool HasLegitimateParent { get; set; }
|
||||
|
||||
/// <summary>进程树深度</summary>
|
||||
public int ProcessTreeDepth { get; set; }
|
||||
|
||||
// ========== 七、DLL加载特征 ==========
|
||||
|
||||
/// <summary>已加载模块数量</summary>
|
||||
public int LoadedModuleCount { get; set; }
|
||||
|
||||
/// <summary>无磁盘路径模块数</summary>
|
||||
public int UnmappedModules { get; set; }
|
||||
|
||||
/// <summary>从临时目录加载的模块数</summary>
|
||||
public int TempPathModules { get; set; }
|
||||
|
||||
// ========== 辅助方法 ==========
|
||||
|
||||
/// <summary>
|
||||
/// 将特征向量转为数值数组,用于 AI 模型输入
|
||||
/// 特征顺序必须与训练时一致
|
||||
/// </summary>
|
||||
public double[] ToFeatureVector()
|
||||
{
|
||||
return new double[]
|
||||
{
|
||||
// 进程元数据 (9维)
|
||||
IsSigned ? 1.0 : 0.0,
|
||||
IsSystemPath ? 1.0 : 0.0,
|
||||
IsTempPath ? 1.0 : 0.0,
|
||||
WorkingSetMB,
|
||||
ThreadCount,
|
||||
IsSystemProcessName ? 1.0 : 0.0,
|
||||
HasLegitimateParent ? 1.0 : 0.0,
|
||||
ProcessTreeDepth,
|
||||
StartTime != default ? (DateTime.Now - StartTime).TotalHours : 0,
|
||||
|
||||
// 内存页权限分布 (7维)
|
||||
TotalRegions,
|
||||
ReadOnlyPages / Math.Max(TotalRegions, 1.0),
|
||||
ReadWritePages / Math.Max(TotalRegions, 1.0),
|
||||
ExecuteReadPages / Math.Max(TotalRegions, 1.0),
|
||||
ExecuteReadWritePages / Math.Max(TotalRegions, 1.0),
|
||||
AnonymousExecPages / Math.Max(TotalRegions, 1.0),
|
||||
RwxPrivatePages,
|
||||
|
||||
// 内存类型分布 (3维)
|
||||
PrivatePages / Math.Max(TotalRegions, 1.0),
|
||||
MappedPages / Math.Max(TotalRegions, 1.0),
|
||||
ImagePages / Math.Max(TotalRegions, 1.0),
|
||||
|
||||
// 代码特征 (6维)
|
||||
HasInMemoryPE ? 1.0 : 0.0,
|
||||
PeMismatchDisk ? 1.0 : 0.0,
|
||||
SuspiciousApiStrings.Count,
|
||||
SuspiciousStringHits,
|
||||
SignatureMatchCount,
|
||||
HighSeverityMatches,
|
||||
|
||||
// 熵特征 (4维)
|
||||
AvgCodeEntropy / 8.0, // 归一化到 [0,1]
|
||||
MaxEntropy / 8.0,
|
||||
HighEntropyRegions,
|
||||
EntropyStdDev / 4.0,
|
||||
|
||||
// 网络特征 (5维)
|
||||
ConnectionCount,
|
||||
ListeningPorts,
|
||||
NonStandardPortConnections,
|
||||
SuspiciousPortConnections,
|
||||
ForeignConnections,
|
||||
|
||||
// DLL加载特征 (3维)
|
||||
LoadedModuleCount,
|
||||
UnmappedModules,
|
||||
TempPathModules
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>特征向量维度</summary>
|
||||
public static int FeatureDimension => 40;
|
||||
}
|
||||
@ -18,8 +18,10 @@ public class ProcessInfo
|
||||
public string Signer { get; set; } = "";
|
||||
public bool IsSystemPath { get; set; }
|
||||
public bool IsTempPath { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
public List<NetworkConnection> Connections { get; set; } = new();
|
||||
public List<MemoryRegion> MemoryRegions { get; set; } = new();
|
||||
public List<string> LoadedModules { get; set; } = new();
|
||||
}
|
||||
|
||||
public class NetworkConnection
|
||||
@ -27,6 +29,7 @@ public class NetworkConnection
|
||||
public string Protocol { get; set; } = "";
|
||||
public string LocalAddr { get; set; } = "";
|
||||
public string RemoteAddr { get; set; } = "";
|
||||
public int RemotePort { get; set; }
|
||||
public string State { get; set; } = "";
|
||||
}
|
||||
|
||||
@ -37,6 +40,7 @@ public class MemoryRegion
|
||||
public string Protect { get; set; } = "";
|
||||
public string Type { get; set; } = "";
|
||||
public string State { get; set; } = "";
|
||||
public double Entropy { get; set; }
|
||||
}
|
||||
|
||||
public class ScanResult
|
||||
@ -49,6 +53,7 @@ public class ScanResult
|
||||
public string Reasoning { get; set; } = "";
|
||||
public List<SignatureMatch> Matches { get; set; } = new();
|
||||
public string AIProvider { get; set; } = "";
|
||||
public string DetectionLayer { get; set; } = "";
|
||||
public DateTime ScanTime { get; set; } = DateTime.Now;
|
||||
}
|
||||
|
||||
|
||||
@ -8,15 +8,26 @@ using MemScanEDR.Models;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
/// <summary>
|
||||
/// AI 分析器 v2.0 —— 重构版
|
||||
/// 核心变化:
|
||||
/// - 默认使用本地 AI 管线(DetectionPipeline:基线+分类+规则三级联动)
|
||||
/// - LLM 大模型仅用于辅助研判(可选,需手动开启)
|
||||
/// - 启发式规则引擎作为降级方案保留
|
||||
/// </summary>
|
||||
public class AIAnalyzer
|
||||
{
|
||||
private readonly HttpClient _http = new();
|
||||
private AIConfig _config;
|
||||
private readonly DetectionPipeline _pipeline;
|
||||
private readonly List<Signature> _signatures;
|
||||
|
||||
public AIAnalyzer(AIConfig config)
|
||||
public AIAnalyzer(AIConfig config, List<Signature> signatures, ScanMode scanMode = ScanMode.Balanced)
|
||||
{
|
||||
_config = config;
|
||||
_signatures = signatures;
|
||||
_http.Timeout = TimeSpan.FromSeconds(config.Timeout);
|
||||
_pipeline = new DetectionPipeline(scanMode);
|
||||
}
|
||||
|
||||
public void UpdateConfig(AIConfig config)
|
||||
@ -25,68 +36,250 @@ public class AIAnalyzer
|
||||
_http.Timeout = TimeSpan.FromSeconds(config.Timeout);
|
||||
}
|
||||
|
||||
public async Task<ScanResult> AnalyzeProcessAsync(ProcessInfo proc, List<SignatureMatch> matches, List<MemoryRegion> suspiciousRegions)
|
||||
/// <summary>
|
||||
/// 分析进程 —— v2.0 核心入口
|
||||
/// </summary>
|
||||
public async Task<ScanResult> AnalyzeProcessAsync(ProcessInfo proc,
|
||||
List<SignatureMatch>? matches = null, List<MemoryRegion>? suspiciousRegions = null)
|
||||
{
|
||||
if (_config.Provider == AIProvider.Heuristic)
|
||||
return HeuristicAnalyze(proc, matches, suspiciousRegions);
|
||||
// v2.0: 优先使用本地 AI 管线
|
||||
if (_config.EnableLocalAI)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _pipeline.AnalyzeAsync(proc, _signatures);
|
||||
|
||||
// Pre-filter: skip API call if nothing suspicious at all
|
||||
// 如果启用了 LLM 辅助研判且本地 AI 判定为可疑/高危
|
||||
if (_config.EnableLLMAssist &&
|
||||
result.RiskLevel != RiskLevel.Safe &&
|
||||
_config.Provider != AIProvider.Heuristic)
|
||||
{
|
||||
var llmResult = await LlmAssistedAnalysis(proc, result);
|
||||
if (llmResult != null)
|
||||
{
|
||||
// LLM 结果作为补充参考,不覆盖本地 AI 的核心判定
|
||||
result.Reasoning += $" | LLM研判: {llmResult.Reasoning}";
|
||||
result.AIProvider += " + LLM";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 管线异常时回退
|
||||
return HeuristicAnalyze(proc, matches ?? new(), suspiciousRegions ?? new(),
|
||||
$"管线异常回退: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// 未启用本地AI时的传统模式
|
||||
if (_config.Provider == AIProvider.Heuristic)
|
||||
{
|
||||
return HeuristicAnalyze(proc, matches ?? new(), suspiciousRegions ?? new());
|
||||
}
|
||||
|
||||
// 传统 LLM 模式(兼容 v1.0)
|
||||
return await LegacyApiAnalyze(proc, matches ?? new(), suspiciousRegions ?? new());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// LLM 辅助研判 —— 仅对本地 AI 已标记为可疑的进程做二次确认
|
||||
/// </summary>
|
||||
private async Task<ScanResult?> LlmAssistedAnalysis(ProcessInfo proc, ScanResult pipelineResult)
|
||||
{
|
||||
try
|
||||
{
|
||||
var prompt = BuildLlmAssistPrompt(proc, pipelineResult);
|
||||
var response = await CallAIAsync(prompt);
|
||||
return ParseLlmResponse(proc, response);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildLlmAssistPrompt(ProcessInfo proc, ScanResult pipelineResult)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("=== 本地AI已标记为可疑的进程 ===");
|
||||
sb.AppendLine($"PID: {proc.Pid}, 进程名: {proc.Name}, 路径: {proc.ExePath}");
|
||||
sb.AppendLine($"风险等级: {pipelineResult.RiskLevel}, 评分: {pipelineResult.RiskScore:F2}");
|
||||
sb.AppendLine($"指标: {string.Join(", ", pipelineResult.Indicators)}");
|
||||
sb.AppendLine($"检测层: {pipelineResult.DetectionLayer}");
|
||||
sb.AppendLine($"本地AI推理: {pipelineResult.Reasoning}");
|
||||
sb.AppendLine("\n=== 请结合以上信息做最终研判 ===");
|
||||
sb.AppendLine("请判断此进程是否为真正的威胁,并给出简短理由。");
|
||||
sb.AppendLine("注意:.NET/Java/Node.js/Chrome等JIT运行时可能有合法RWX内存,安全软件可能有注入行为。");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private ScanResult ParseLlmResponse(ProcessInfo proc, string content)
|
||||
{
|
||||
return new ScanResult
|
||||
{
|
||||
Pid = proc.Pid,
|
||||
ProcessName = proc.Name,
|
||||
RiskLevel = RiskLevel.Safe,
|
||||
RiskScore = 0,
|
||||
Reasoning = content.Trim().Length > 200 ? content.Trim()[..200] : content.Trim(),
|
||||
AIProvider = $"llm-assist/{_config.Model}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 传统 LLM API 分析(兼容 v1.0 模式)
|
||||
/// </summary>
|
||||
private async Task<ScanResult> LegacyApiAnalyze(ProcessInfo proc,
|
||||
List<SignatureMatch> matches, List<MemoryRegion> suspiciousRegions)
|
||||
{
|
||||
var preScore = QuickPreScore(proc, matches, suspiciousRegions);
|
||||
if (preScore < 0.05)
|
||||
{
|
||||
return new ScanResult
|
||||
{
|
||||
Pid = proc.Pid,
|
||||
ProcessName = proc.Name,
|
||||
RiskLevel = RiskLevel.Safe,
|
||||
RiskScore = 0.0,
|
||||
Indicators = new List<string>(),
|
||||
Reasoning = $"PID {proc.Pid} ({proc.Name}) | no suspicious indicators, skipped AI analysis",
|
||||
AIProvider = "heuristic/pre-filter",
|
||||
ScanTime = DateTime.Now
|
||||
Pid = proc.Pid,
|
||||
ProcessName = proc.Name,
|
||||
RiskLevel = RiskLevel.Safe,
|
||||
RiskScore = 0.0,
|
||||
Indicators = new List<string>(),
|
||||
Reasoning = $"PID {proc.Pid} ({proc.Name}) | v2.0 预过滤跳过",
|
||||
AIProvider = "heuristic/pre-filter-v2",
|
||||
DetectionLayer = "PreFilter",
|
||||
ScanTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var prompt = BuildPrompt(proc, matches, suspiciousRegions);
|
||||
var prompt = BuildLegacyPrompt(proc, matches, suspiciousRegions);
|
||||
var response = await CallAIAsync(prompt);
|
||||
return ParseResponse(proc, response);
|
||||
return ParseLegacyResponse(proc, response);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var result = HeuristicAnalyze(proc, matches, suspiciousRegions);
|
||||
result.Reasoning += $" [AI API error: {ex.Message}]";
|
||||
return result;
|
||||
return HeuristicAnalyze(proc, matches, suspiciousRegions,
|
||||
$"API异常回退: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static double QuickPreScore(ProcessInfo proc, List<SignatureMatch> matches, List<MemoryRegion> suspiciousRegions)
|
||||
private static double QuickPreScore(ProcessInfo proc, List<SignatureMatch> matches,
|
||||
List<MemoryRegion> suspiciousRegions)
|
||||
{
|
||||
double s = 0;
|
||||
if (!proc.IsSigned && proc.IsTempPath) s += 0.30;
|
||||
var sysNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "svchost.exe", "csrss.exe", "lsass.exe", "winlogon.exe", "services.exe" };
|
||||
if (sysNames.Contains(proc.Name) && !proc.IsSystemPath) s += 0.25;
|
||||
if (FeatureExtractor.IsSystemProcessName(proc.Name) && !proc.IsSystemPath) s += 0.25;
|
||||
var suspPorts = new HashSet<int> { 4444, 5555, 6666, 7777, 1337, 31337, 9999, 12345, 27015 };
|
||||
if (proc.Connections.Any(c => { var parts = c.RemoteAddr.Split(':'); return parts.Length >= 2 && int.TryParse(parts[^1], out var p) && suspPorts.Contains(p); })) s += 0.20;
|
||||
if (proc.Connections.Any(c => suspPorts.Contains(c.RemotePort))) s += 0.20;
|
||||
if (matches.Count > 0) s += 0.20;
|
||||
if (suspiciousRegions.Any(r => r.Protect.Contains("EXECUTE_READWRITE"))) s += 0.15;
|
||||
return Math.Min(s, 1.0);
|
||||
}
|
||||
|
||||
// ─── 启发式规则(保留作为降级方案)───
|
||||
public ScanResult HeuristicAnalyze(ProcessInfo proc, List<SignatureMatch> matches,
|
||||
List<MemoryRegion> suspiciousRegions, string? errorNote = null)
|
||||
{
|
||||
double score = 0;
|
||||
var indicators = new List<string>();
|
||||
var reasons = new List<string>();
|
||||
|
||||
// Rule 1: unsigned + temp path
|
||||
if (!proc.IsSigned && proc.IsTempPath)
|
||||
{
|
||||
score += 0.30;
|
||||
indicators.Add("unsigned_temp_path");
|
||||
reasons.Add("unsigned executable in temp directory");
|
||||
}
|
||||
|
||||
// Rule 2: process masquerading
|
||||
if (FeatureExtractor.IsSystemProcessName(proc.Name) && !proc.IsSystemPath)
|
||||
{
|
||||
score += 0.25;
|
||||
indicators.Add("process_masquerading");
|
||||
reasons.Add("system process name in non-system path");
|
||||
}
|
||||
|
||||
// Rule 3: suspicious ports
|
||||
var suspPorts = new HashSet<int> { 4444, 5555, 6666, 7777, 1337, 31337 };
|
||||
foreach (var conn in proc.Connections)
|
||||
{
|
||||
if (suspPorts.Contains(conn.RemotePort))
|
||||
{
|
||||
score += 0.20;
|
||||
indicators.Add("suspicious_port");
|
||||
reasons.Add($"suspicious remote port {conn.RemotePort}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 4: signature matches
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
var high = matches.Count(m => m.Severity == "high");
|
||||
var med = matches.Count(m => m.Severity == "medium");
|
||||
score += Math.Min(high * 0.20, 0.4);
|
||||
score += Math.Min(med * 0.08, 0.2);
|
||||
if (high > 0) { indicators.Add("high_severity_match"); reasons.Add($"{high} high-severity signatures"); }
|
||||
if (matches.Any(m => m.Category == "shellcode")) { score += 0.15; indicators.Add("shellcode_detected"); }
|
||||
if (matches.Any(m => m.Category == "c2")) { score += 0.20; indicators.Add("c2_pattern"); }
|
||||
}
|
||||
|
||||
// Rule 5: suspicious memory regions (v2.0 improved: distinguish JIT)
|
||||
var rwxCount = suspiciousRegions.Count(r =>
|
||||
r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE");
|
||||
|
||||
// v2.0 improvement: don't flag JIT runtimes
|
||||
var isRuntime = proc.Name.ToLowerInvariant() switch
|
||||
{
|
||||
var n when n.Contains("dotnet") || n.Contains("java") ||
|
||||
n.Contains("node") || n.Contains("python") ||
|
||||
n.Contains("chrome") || n.Contains("msedge") ||
|
||||
n.Contains("firefox") => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
if (rwxCount > 0 && !isRuntime)
|
||||
{
|
||||
score += Math.Min(rwxCount * 0.12, 0.25);
|
||||
indicators.Add("rwx_private_memory");
|
||||
}
|
||||
|
||||
score = Math.Min(score, 1.0);
|
||||
var level = score >= 0.75 ? RiskLevel.High : score >= 0.50 ? RiskLevel.Suspicious : RiskLevel.Safe;
|
||||
|
||||
var reasoning = $"PID {proc.Pid} ({proc.Name}) | {string.Join("; ", reasons)} | score={score:F2}";
|
||||
if (errorNote != null)
|
||||
reasoning += $" | {errorNote}";
|
||||
|
||||
return new ScanResult
|
||||
{
|
||||
Pid = proc.Pid,
|
||||
ProcessName = proc.Name,
|
||||
RiskLevel = level,
|
||||
RiskScore = score,
|
||||
Indicators = indicators,
|
||||
Reasoning = reasoning,
|
||||
AIProvider = "heuristic/rule-engine-v2",
|
||||
DetectionLayer = "Heuristic",
|
||||
ScanTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
// ─── LLM API 调用 ───
|
||||
private async Task<string> CallAIAsync(string userMessage)
|
||||
{
|
||||
if (_config.Provider == AIProvider.Claude)
|
||||
return await CallClaudeAsync(userMessage);
|
||||
|
||||
// OpenAI-compatible / Local use the same OpenAI chat format
|
||||
var url = _config.Provider == AIProvider.Local
|
||||
? $"{_config.BaseUrl}/api/chat"
|
||||
: $"{_config.BaseUrl.TrimEnd('/')}/chat/completions";
|
||||
|
||||
object requestBody = _config.Provider == AIProvider.Local
|
||||
? new { model = _config.Model, messages = new[] { new { role = "system", content = SystemPrompt }, new { role = "user", content = userMessage } }, stream = false }
|
||||
: new { model = _config.Model, messages = new[] { new { role = "system", content = SystemPrompt }, new { role = "user", content = userMessage } }, max_tokens = _config.MaxTokens, temperature = _config.Temperature };
|
||||
? new { model = _config.Model, messages = new[] { new { role = "system", content = V2SystemPrompt }, new { role = "user", content = userMessage } }, stream = false }
|
||||
: new { model = _config.Model, messages = new[] { new { role = "system", content = V2SystemPrompt }, new { role = "user", content = userMessage } }, max_tokens = _config.MaxTokens, temperature = _config.Temperature };
|
||||
|
||||
var json = JsonSerializer.Serialize(requestBody);
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
||||
@ -100,10 +293,8 @@ public class AIAnalyzer
|
||||
var responseJson = await response.Content.ReadAsStringAsync();
|
||||
|
||||
using var doc = JsonDocument.Parse(responseJson);
|
||||
|
||||
if (_config.Provider == AIProvider.Local)
|
||||
return doc.RootElement.GetProperty("message").GetProperty("content").GetString() ?? "";
|
||||
|
||||
return doc.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString() ?? "";
|
||||
}
|
||||
|
||||
@ -114,7 +305,7 @@ public class AIAnalyzer
|
||||
{
|
||||
model = _config.Model,
|
||||
max_tokens = _config.MaxTokens,
|
||||
system = SystemPrompt,
|
||||
system = V2SystemPrompt,
|
||||
messages = new[] { new { role = "user", content = userMessage } }
|
||||
};
|
||||
|
||||
@ -132,27 +323,62 @@ public class AIAnalyzer
|
||||
return doc.RootElement.GetProperty("content")[0].GetProperty("text").GetString() ?? "";
|
||||
}
|
||||
|
||||
private ScanResult ParseResponse(ProcessInfo proc, string content)
|
||||
private string BuildLegacyPrompt(ProcessInfo proc, List<SignatureMatch> matches,
|
||||
List<MemoryRegion> suspiciousRegions)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("=== PROCESS DATA ===");
|
||||
sb.AppendLine($"PID: {proc.Pid}");
|
||||
sb.AppendLine($"Name: {proc.Name}");
|
||||
sb.AppendLine($"Path: {(string.IsNullOrEmpty(proc.ExePath) ? "unknown" : proc.ExePath)}");
|
||||
sb.AppendLine($"Parent: {proc.ParentName} (PPID: {proc.Ppid})");
|
||||
sb.AppendLine($"CommandLine: {proc.CmdLine}");
|
||||
sb.AppendLine($"Signed: {proc.IsSigned}");
|
||||
sb.AppendLine($"Signer: {(string.IsNullOrEmpty(proc.Signer) ? "-" : proc.Signer)}");
|
||||
sb.AppendLine($"SystemPath: {proc.IsSystemPath}");
|
||||
sb.AppendLine($"TempPath: {proc.IsTempPath}");
|
||||
|
||||
if (proc.Connections.Count > 0)
|
||||
{
|
||||
sb.AppendLine($"\nNetworkConnections ({proc.Connections.Count}):");
|
||||
foreach (var c in proc.Connections.Take(8))
|
||||
sb.AppendLine($" {c.Protocol} {c.LocalAddr} -> {c.RemoteAddr} [{c.State}]");
|
||||
}
|
||||
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
sb.AppendLine($"\nMemorySignatures ({matches.Count}):");
|
||||
foreach (var m in matches.Take(10))
|
||||
sb.AppendLine($" [{m.Severity}] {m.SigId} @ {m.Address}: {m.Description}");
|
||||
}
|
||||
|
||||
if (suspiciousRegions.Count > 0)
|
||||
{
|
||||
sb.AppendLine($"\nSuspiciousMemoryRegions ({suspiciousRegions.Count}):");
|
||||
foreach (var r in suspiciousRegions.Take(5))
|
||||
sb.AppendLine($" 0x{r.BaseAddress:X} size={r.Size} protect={r.Protect} type={r.Type}");
|
||||
}
|
||||
|
||||
sb.AppendLine("\n=== ANALYZE THIS PROCESS ===");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static ScanResult ParseLegacyResponse(ProcessInfo proc, string content)
|
||||
{
|
||||
content = content.Trim();
|
||||
if (content.StartsWith("```"))
|
||||
{
|
||||
var lines = content.Split('\n');
|
||||
content = string.Join('\n', lines[1..^1]);
|
||||
}
|
||||
content = string.Join('\n', content.Split('\n')[1..^1]);
|
||||
|
||||
try
|
||||
{
|
||||
using var doc = JsonDocument.Parse(content);
|
||||
var root = doc.RootElement;
|
||||
|
||||
var riskLevel = root.GetProperty("risk_level").GetString() switch
|
||||
{
|
||||
"high" => RiskLevel.High,
|
||||
"suspicious" => RiskLevel.Suspicious,
|
||||
_ => RiskLevel.Safe
|
||||
};
|
||||
|
||||
var indicators = new List<string>();
|
||||
if (root.TryGetProperty("indicators", out var indArr))
|
||||
foreach (var ind in indArr.EnumerateArray())
|
||||
@ -166,7 +392,8 @@ public class AIAnalyzer
|
||||
RiskScore = root.GetProperty("risk_score").GetDouble(),
|
||||
Indicators = indicators,
|
||||
Reasoning = root.GetProperty("reasoning").GetString() ?? "",
|
||||
AIProvider = $"{_config.Provider}/{_config.Model}",
|
||||
AIProvider = "llm-api-v2",
|
||||
DetectionLayer = "LLM",
|
||||
ScanTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
@ -179,139 +406,21 @@ public class AIAnalyzer
|
||||
RiskLevel = RiskLevel.Safe,
|
||||
RiskScore = 0,
|
||||
Reasoning = content,
|
||||
AIProvider = $"{_config.Provider}/{_config.Model}",
|
||||
AIProvider = "llm-api-v2",
|
||||
DetectionLayer = "LLM",
|
||||
ScanTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildPrompt(ProcessInfo proc, List<SignatureMatch> matches, List<MemoryRegion> suspiciousRegions)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("=== PROCESS DATA ===");
|
||||
sb.AppendLine($"PID: {proc.Pid}");
|
||||
sb.AppendLine($"Name: {proc.Name}");
|
||||
sb.AppendLine($"Path: {(string.IsNullOrEmpty(proc.ExePath) ? "unknown" : proc.ExePath)}");
|
||||
sb.AppendLine($"Parent: {proc.ParentName} (PPID: {proc.Ppid})");
|
||||
sb.AppendLine($"CommandLine: {proc.CmdLine}");
|
||||
sb.AppendLine($"Signed: {proc.IsSigned}");
|
||||
sb.AppendLine($"Signer: {(string.IsNullOrEmpty(proc.Signer) ? "-" : proc.Signer)}");
|
||||
sb.AppendLine($"SystemPath: {proc.IsSystemPath}");
|
||||
sb.AppendLine($"TempPath: {proc.IsTempPath}");
|
||||
sb.AppendLine($"ThreadCount: {proc.ThreadCount}");
|
||||
sb.AppendLine($"WorkingSetMB: {proc.WorkingSetMB}");
|
||||
|
||||
if (proc.Connections.Count > 0)
|
||||
{
|
||||
sb.AppendLine($"\nNetworkConnections ({proc.Connections.Count}):");
|
||||
foreach (var c in proc.Connections.Take(8))
|
||||
sb.AppendLine($" {c.Protocol} {c.LocalAddr} -> {c.RemoteAddr} [{c.State}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("\nNetworkConnections: none");
|
||||
}
|
||||
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
sb.AppendLine($"\nMemorySignatures ({matches.Count}):");
|
||||
foreach (var m in matches.Take(10))
|
||||
sb.AppendLine($" [{m.Severity}] [{m.Category}] {m.SigId} @ {m.Address}: {m.Description}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("\nMemorySignatures: none");
|
||||
}
|
||||
|
||||
if (suspiciousRegions.Count > 0)
|
||||
{
|
||||
sb.AppendLine($"\nSuspiciousMemoryRegions ({suspiciousRegions.Count}):");
|
||||
foreach (var r in suspiciousRegions.Take(5))
|
||||
sb.AppendLine($" 0x{r.BaseAddress:X} size={r.Size} protect={r.Protect} type={r.Type} state={r.State}");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("\nSuspiciousMemoryRegions: none");
|
||||
}
|
||||
|
||||
sb.AppendLine("\n=== ANALYZE THIS PROCESS ===");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public ScanResult HeuristicAnalyze(ProcessInfo proc, List<SignatureMatch> matches, List<MemoryRegion> suspiciousRegions)
|
||||
{
|
||||
double score = 0;
|
||||
var indicators = new List<string>();
|
||||
var reasons = new List<string>();
|
||||
|
||||
// Rule 1: unsigned + temp path
|
||||
if (!proc.IsSigned && proc.IsTempPath)
|
||||
{
|
||||
score += 0.30;
|
||||
indicators.Add("unsigned_temp_path");
|
||||
reasons.Add("unsigned executable in temp directory");
|
||||
}
|
||||
|
||||
// Rule 2: process masquerading
|
||||
var sysNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
|
||||
"svchost.exe", "csrss.exe", "lsass.exe", "winlogon.exe", "services.exe"
|
||||
};
|
||||
if (sysNames.Contains(proc.Name) && !proc.IsSystemPath)
|
||||
{
|
||||
score += 0.25;
|
||||
indicators.Add("process_masquerading");
|
||||
reasons.Add($"system process name in non-system path");
|
||||
}
|
||||
|
||||
// Rule 3: suspicious ports
|
||||
var suspPorts = new HashSet<int> { 4444, 5555, 6666, 7777, 1337, 31337 };
|
||||
foreach (var conn in proc.Connections)
|
||||
{
|
||||
var parts = conn.RemoteAddr.Split(':');
|
||||
if (parts.Length == 2 && int.TryParse(parts[1], out var port) && suspPorts.Contains(port))
|
||||
{
|
||||
score += 0.20;
|
||||
indicators.Add("suspicious_port");
|
||||
reasons.Add($"suspicious remote port {port}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Rule 4: signature matches
|
||||
if (matches.Count > 0)
|
||||
{
|
||||
var high = matches.Count(m => m.Severity == "high");
|
||||
var med = matches.Count(m => m.Severity == "medium");
|
||||
score += Math.Min(high * 0.20, 0.4);
|
||||
score += Math.Min(med * 0.08, 0.2);
|
||||
if (high > 0) { indicators.Add("high_severity_match"); reasons.Add($"{high} high-severity signatures"); }
|
||||
if (matches.Any(m => m.Category == "shellcode")) { score += 0.15; indicators.Add("shellcode_detected"); }
|
||||
if (matches.Any(m => m.Category == "c2")) { score += 0.20; indicators.Add("c2_pattern"); }
|
||||
}
|
||||
|
||||
// Rule 5: suspicious memory regions
|
||||
var rwxCount = suspiciousRegions.Count(r => r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE");
|
||||
if (rwxCount > 0) { score += Math.Min(rwxCount * 0.12, 0.25); indicators.Add("rwx_private_memory"); }
|
||||
|
||||
score = Math.Min(score, 1.0);
|
||||
var level = score >= 0.75 ? RiskLevel.High : score >= 0.50 ? RiskLevel.Suspicious : RiskLevel.Safe;
|
||||
|
||||
return new ScanResult
|
||||
{
|
||||
Pid = proc.Pid,
|
||||
ProcessName = proc.Name,
|
||||
RiskLevel = level,
|
||||
RiskScore = score,
|
||||
Indicators = indicators,
|
||||
Reasoning = $"PID {proc.Pid} ({proc.Name}) | {string.Join("; ", reasons)} | score={score:F2}",
|
||||
AIProvider = "heuristic/rule-engine-v1",
|
||||
ScanTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<(bool ok, string message)> TestConnectionAsync()
|
||||
{
|
||||
if (_config.Provider == AIProvider.Heuristic) return (true, "Heuristic mode (no API)");
|
||||
if (_config.Provider == AIProvider.Heuristic)
|
||||
{
|
||||
return _config.EnableLocalAI
|
||||
? (true, "AI 管线模式 (Pipeline v2.0, 无需 API)")
|
||||
: (true, "启发式规则模式 (Heuristic v2.0, 无需 API)");
|
||||
}
|
||||
try
|
||||
{
|
||||
await CallAIAsync("ping");
|
||||
@ -320,53 +429,22 @@ public class AIAnalyzer
|
||||
catch (Exception ex) { return (false, ex.Message); }
|
||||
}
|
||||
|
||||
private const string SystemPrompt = @"You are a Windows memory security expert analyzing a single process for threats.
|
||||
private const string V2SystemPrompt = @"You are a Windows memory security expert (v2.0) analyzing process threat levels.
|
||||
|
||||
=== YOUR TASK ===
|
||||
Given process metadata, memory scan results, and memory region data, determine if the process is safe, suspicious, or high risk.
|
||||
=== CORE PRINCIPLE ===
|
||||
The VAST majority (>95%) of processes are SAFE. Default to ""safe"" unless you see CLEAR threat evidence.
|
||||
|
||||
=== RISK LEVEL DEFINITIONS ===
|
||||
=== CRITICAL FALSE POSITIVE PREVENTION ===
|
||||
1. .NET/Java/Node.js/Chrome/V8 processes LEGITIMATELY use RWX memory for JIT compilation. This is NOT suspicious.
|
||||
2. Security/EDR products legitimately inject/hook processes. Their RWX + injection API usage is NORMAL.
|
||||
3. Development/debugging tools legitimately use Process inspection APIs.
|
||||
4. A single indicator ALONE is never enough for ""high"" risk.
|
||||
|
||||
""safe"" — The process exhibits NORMAL or benign behavior:
|
||||
- Signed binaries from trusted publishers (Microsoft, Adobe, major vendors)
|
||||
- DLL injection by standard antivirus/EDR components
|
||||
- Internet connections to normal CDNs/cloud services (Azure, AWS, Google)
|
||||
- Small private RWX regions used by .NET JIT, JavaScript engines (node.js), or Java
|
||||
- Hooking by regular security products
|
||||
- Processes in System32 that are known Windows components
|
||||
**IMPORTANT: The vast majority (>95%) of processes are SAFE. Default to ""safe"" unless you have clear threat evidence.**
|
||||
=== RISK LEVELS ===
|
||||
""safe"": Normal benign process, even with minor deviations (< 0.30)
|
||||
""suspicious"": Multiple weak signals or single strong signal (0.30-0.70)
|
||||
""high"": Clear evidence with multiple concurring signals (> 0.70)
|
||||
|
||||
""suspicious"" — ONE or TWO of the following are present:
|
||||
- Unsigned executable in temporary folder (%TEMP%, Downloads)
|
||||
- Process with system-critical name (svchost.exe, lsass.exe, csrss.exe) running from non-system path
|
||||
- Known C2 ports (4444, 5555, 6666, 1337, etc.)
|
||||
- Multiple large PAGE_EXECUTE_READWRITE private regions
|
||||
- Signature matches of medium severity
|
||||
- Unsigned DLL loaded from suspicious path
|
||||
**A process needs at least 2-3 weak signals to be suspicious, or 1 strong signal.**
|
||||
|
||||
""high"" — CLEAR evidence of malware, requiring MULTIPLE concurring signals:
|
||||
- Shellcode signature match + RWX private memory + unsigned temp path (all 3 together)
|
||||
- C2 pattern match + network to suspicious remote address
|
||||
- Reflective DLL injection signature + PE header in private memory
|
||||
- Process hollowing or process injection signature matches
|
||||
**Single indicators are NOT enough for ""high"". Requires 3+ concurring indicators.**
|
||||
|
||||
=== JSON RESPONSE FORMAT ===
|
||||
Respond with ONLY this JSON (no markdown, no explanation):
|
||||
{""risk_level"":""safe"",""risk_score"":0.0,""indicators"":[],""reasoning"":""""}
|
||||
|
||||
Fields:
|
||||
- risk_level: ""safe"" | ""suspicious"" | ""high""
|
||||
- risk_score: 0.0–1.0 (0.0–0.30=safe, 0.31–0.69=suspicious, 0.70–1.0=high)
|
||||
- indicators: list of 0–5 key indicator strings (e.g. ""unsigned_temp_binary"", ""rwx_shellcode_region"", ""c2_network"", ""process_masquerading"", ""dll_injection_sig"")
|
||||
- reasoning: 1–2 sentence summary
|
||||
|
||||
=== IMPORTANT RULES ===
|
||||
1. Error on the side of ""safe"". Only elevate when you see CONCRETE threat indicators.
|
||||
2. A memory signature match alone does NOT make a process malicious — many legitimate tools use similar techniques.
|
||||
3. .NET/Java/Node.js processes legitimately use RWX memory — this alone is NOT suspicious.
|
||||
4. Consider the process name and path context — calc.exe in System32 is safe; calc.exe in %TEMP% is not.
|
||||
5. Process injection indicators combined with unsigned temp binaries is dangerous.
|
||||
6. Only list indicators that are actually present — never fabricate evidence.";
|
||||
=== JSON RESPONSE ===
|
||||
{""risk_level"":""safe"",""risk_score"":0.0,""indicators"":[],""reasoning"":""""}";
|
||||
}
|
||||
|
||||
175
src-csharp/MemScanEDR/Services/BaselineEngine.cs
Normal file
175
src-csharp/MemScanEDR/Services/BaselineEngine.cs
Normal file
@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MemScanEDR.Models;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 无监督基线检测引擎 —— 第一层过滤
|
||||
/// 使用统计异常检测方法(Z-score + 重构误差),无需恶意样本训练
|
||||
/// 核心逻辑:正常程序内存结构高度规整,恶意程序偏离基线
|
||||
/// </summary>
|
||||
public class BaselineEngine
|
||||
{
|
||||
private readonly BaselineLibrary _baselineLibrary;
|
||||
private readonly double _globalAnomalyThreshold;
|
||||
|
||||
public BaselineEngine(BaselineLibrary? library = null, double threshold = 0.50)
|
||||
{
|
||||
_baselineLibrary = library ?? BaselineLibrary.CreateDefault();
|
||||
_globalAnomalyThreshold = threshold;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分析进程特征,返回异常分数和判定结果
|
||||
/// </summary>
|
||||
public BaselineResult Analyze(MemoryFeatures features)
|
||||
{
|
||||
var result = new BaselineResult();
|
||||
|
||||
// 1. 找到最匹配的基线
|
||||
var baseline = _baselineLibrary.FindBestMatch(features.ProcessName, features.ExePath);
|
||||
if (baseline == null)
|
||||
{
|
||||
result.IsAnomalous = false;
|
||||
result.AnomalyScore = 0;
|
||||
result.Details = "无匹配基线,跳过异常检测";
|
||||
return result;
|
||||
}
|
||||
|
||||
result.BaselineName = baseline.Name;
|
||||
|
||||
// 2. 转换特征为向量
|
||||
var featureVector = features.ToFeatureVector();
|
||||
|
||||
// 3. 计算 Z-score 异常分数 (Mahalanobis-like)
|
||||
var (anomalyScore, deviantFeatures) = baseline.CalculateAnomalyScore(featureVector);
|
||||
|
||||
// 4. 计算重构误差
|
||||
var reconstructionError = baseline.CalculateReconstructionError(featureVector);
|
||||
|
||||
// 5. 综合分数:Z-score 异常 + 重构误差加权
|
||||
var combinedScore = anomalyScore * 0.6 + reconstructionError * 0.4;
|
||||
combinedScore = Math.Min(combinedScore, 1.0);
|
||||
|
||||
result.AnomalyScore = combinedScore;
|
||||
result.DeviantFeatureCount = deviantFeatures.Count;
|
||||
result.DeviantFeatureIndices = deviantFeatures;
|
||||
result.ReconstructionError = reconstructionError;
|
||||
result.IsAnomalous = combinedScore > _globalAnomalyThreshold;
|
||||
|
||||
// 6. 生成可解释性说明
|
||||
result.Details = GenerateExplanation(features, baseline, deviantFeatures, combinedScore);
|
||||
|
||||
// 7. 判定分类
|
||||
result.Classification = combinedScore switch
|
||||
{
|
||||
< 0.25 => BaselineClassification.Normal,
|
||||
< 0.50 => BaselineClassification.BenignVariance,
|
||||
< 0.75 => BaselineClassification.Anomalous,
|
||||
_ => BaselineClassification.HighlyAnomalous
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快速预判 —— 仅基于元数据,不读内存
|
||||
/// </summary>
|
||||
public BaselineResult QuickPreAnalyze(ProcessInfo proc, List<MemoryRegion> regions)
|
||||
{
|
||||
var extractor = new FeatureExtractor();
|
||||
var features = extractor.QuickExtract(proc, regions);
|
||||
return Analyze(features);
|
||||
}
|
||||
|
||||
private static string GenerateExplanation(MemoryFeatures f, BaselineProfile baseline,
|
||||
List<int> deviantFeatures, double score)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (score < 0.25)
|
||||
{
|
||||
parts.Add("进程内存结构符合正常程序基线");
|
||||
return string.Join("; ", parts);
|
||||
}
|
||||
|
||||
// 命名特征索引对应的含义
|
||||
var featureNames = new Dictionary<int, string>
|
||||
{
|
||||
{0, "未签名"}, {1, "非系统路径"}, {2, "临时路径"},
|
||||
{5, "系统进程名"}, {6, "父进程异常"},
|
||||
{12, "RWX页比例高"}, {13, "匿名可执行页"}, {14, "RWX私有页"},
|
||||
{18, "内存PE头"}, {19, "PE与磁盘不一致"},
|
||||
{20, "可疑API"}, {21, "敏感字符串"}, {22, "签名匹配"}, {23, "高危签名"},
|
||||
{24, "高熵代码段"}, {25, "最大熵值"}, {26, "高熵区域数"},
|
||||
{29, "非标准端口连接"}, {30, "可疑端口"}, {31, "境外连接"},
|
||||
{35, "无磁盘映射模块"}, {36, "临时目录模块"}
|
||||
};
|
||||
|
||||
foreach (var idx in deviantFeatures.Take(5))
|
||||
{
|
||||
if (featureNames.TryGetValue(idx, out var name))
|
||||
parts.Add(name);
|
||||
}
|
||||
|
||||
parts.Add($"异常分数={score:F2}");
|
||||
|
||||
if (f.RwxPrivatePages > 0 && f.IsSystemProcessName)
|
||||
parts.Insert(0, "严重警告: 系统进程存在RWX私有内存");
|
||||
|
||||
if (f.PeMismatchDisk)
|
||||
parts.Insert(0, "严重警告: Process Hollowing 检测");
|
||||
|
||||
return string.Join("; ", parts);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基线检测结果
|
||||
/// </summary>
|
||||
public class BaselineResult
|
||||
{
|
||||
/// <summary>使用的基线名称</summary>
|
||||
public string BaselineName { get; set; } = "";
|
||||
|
||||
/// <summary>综合异常分数 [0-1]</summary>
|
||||
public double AnomalyScore { get; set; }
|
||||
|
||||
/// <summary>是否判定为异常</summary>
|
||||
public bool IsAnomalous { get; set; }
|
||||
|
||||
/// <summary>偏离特征数量</summary>
|
||||
public int DeviantFeatureCount { get; set; }
|
||||
|
||||
/// <summary>偏离特征索引列表</summary>
|
||||
public List<int> DeviantFeatureIndices { get; set; } = new();
|
||||
|
||||
/// <summary>重构误差</summary>
|
||||
public double ReconstructionError { get; set; }
|
||||
|
||||
/// <summary>分类结果</summary>
|
||||
public BaselineClassification Classification { get; set; }
|
||||
|
||||
/// <summary>可解释性详情</summary>
|
||||
public string Details { get; set; } = "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基线分类结果
|
||||
/// </summary>
|
||||
public enum BaselineClassification
|
||||
{
|
||||
/// <summary>完全正常</summary>
|
||||
Normal,
|
||||
|
||||
/// <summary>良性偏差(如开发工具、安全软件的正常表现)</summary>
|
||||
BenignVariance,
|
||||
|
||||
/// <summary>异常偏离基线</summary>
|
||||
Anomalous,
|
||||
|
||||
/// <summary>高度异常</summary>
|
||||
HighlyAnomalous
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
public class CeMcpManager
|
||||
{
|
||||
private Process? _process;
|
||||
public bool IsRunning => _process != null && !_process.HasExited;
|
||||
|
||||
public string FindServerPath()
|
||||
{
|
||||
var candidates = new[]
|
||||
{
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "src", "ce_mcp_server", "server.py"),
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ce_mcp_server", "server.py"),
|
||||
@"d:\github\ce-mcp-server\server.py",
|
||||
@"d:\github\MemScan-EDR\src\ce_mcp_server\server.py",
|
||||
Path.Combine(Directory.GetCurrentDirectory(), "src", "ce_mcp_server", "server.py"),
|
||||
};
|
||||
foreach (var c in candidates)
|
||||
if (File.Exists(c)) return c;
|
||||
return "";
|
||||
}
|
||||
|
||||
public bool Start(string pythonCommand = "python", string? serverPath = null)
|
||||
{
|
||||
if (IsRunning) return true;
|
||||
serverPath ??= FindServerPath();
|
||||
if (string.IsNullOrEmpty(serverPath) || !File.Exists(serverPath)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
_process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = pythonCommand,
|
||||
Arguments = $"\"{serverPath}\"",
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
},
|
||||
EnableRaisingEvents = true
|
||||
};
|
||||
_process.Start();
|
||||
return true;
|
||||
}
|
||||
catch { return false; }
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (_process != null && !_process.HasExited)
|
||||
{
|
||||
try { _process.Kill(); } catch { }
|
||||
_process.Dispose();
|
||||
_process = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
237
src-csharp/MemScanEDR/Services/ClassificationEngine.cs
Normal file
237
src-csharp/MemScanEDR/Services/ClassificationEngine.cs
Normal file
@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MemScanEDR.Models;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 监督学习分类引擎 —— 第二层检测
|
||||
/// 使用轻量级加权特征分类器,模拟训练好的监督模型的推理行为
|
||||
/// 不依赖深度学习框架,纯 C# 实现,适合终端 EDR 部署
|
||||
///
|
||||
/// 分类逻辑基于:
|
||||
/// 1. 特征加权评分(模拟训练好的线性模型权重)
|
||||
/// 2. 特征组合规则(模拟决策树的关键分支)
|
||||
/// 3. 已知恶意模式匹配
|
||||
/// </summary>
|
||||
public class ClassificationEngine
|
||||
{
|
||||
// ─── 特征权重(从训练数据中学习到的权重,模拟线性分类器)───
|
||||
// 正权重 = 该特征越高越可疑,负权重 = 正常特征
|
||||
private static readonly double[] FeatureWeights = new double[]
|
||||
{
|
||||
// 进程元数据
|
||||
-2.0, -1.5, 3.0, 0.1, 0.1, 2.5, -2.0, 0.1, 0.0,
|
||||
// 内存页权限(RWX 相关权重极高)
|
||||
0.1, -0.5, -0.3, -1.0, 4.0, 3.5, 2.5,
|
||||
// 内存类型
|
||||
-0.5, -0.3, -1.0,
|
||||
// 代码特征
|
||||
1.5, 4.0, 2.0, 2.5, 1.5, 2.0,
|
||||
// 熵特征
|
||||
1.0, 2.5, 2.0, 1.0,
|
||||
// 网络特征
|
||||
0.5, 0.3, 1.0, 2.5, 1.5,
|
||||
// DLL加载
|
||||
0.2, 2.0, 2.5
|
||||
};
|
||||
|
||||
// 分类阈值
|
||||
private const double HighRiskThreshold = 0.70;
|
||||
private const double SuspiciousThreshold = 0.40;
|
||||
|
||||
/// <summary>
|
||||
/// 对特征向量进行分类
|
||||
/// </summary>
|
||||
public ClassificationResult Classify(MemoryFeatures features)
|
||||
{
|
||||
var result = new ClassificationResult();
|
||||
var vector = features.ToFeatureVector();
|
||||
|
||||
// ─── 1. 线性加权评分 ───
|
||||
double weightedScore = 0;
|
||||
double maxPossibleScore = 0;
|
||||
var topContributors = new List<(int index, double contribution)>();
|
||||
|
||||
for (int i = 0; i < Math.Min(vector.Length, FeatureWeights.Length); i++)
|
||||
{
|
||||
var contribution = vector[i] * FeatureWeights[i];
|
||||
weightedScore += contribution;
|
||||
|
||||
if (FeatureWeights[i] > 0)
|
||||
maxPossibleScore += Math.Abs(FeatureWeights[i]);
|
||||
|
||||
if (Math.Abs(contribution) > 0.5)
|
||||
topContributors.Add((i, contribution));
|
||||
}
|
||||
|
||||
// 归一化到 [0, 1]
|
||||
var normalizedScore = maxPossibleScore > 0
|
||||
? Math.Max(0, weightedScore) / (maxPossibleScore * 0.5)
|
||||
: 0;
|
||||
normalizedScore = Math.Min(normalizedScore, 1.0);
|
||||
|
||||
// ─── 2. 决策树规则增强 ───
|
||||
var ruleBoost = ApplyDecisionRules(features);
|
||||
|
||||
// 综合得分
|
||||
var finalScore = normalizedScore * 0.7 + ruleBoost * 0.3;
|
||||
finalScore = Math.Min(finalScore, 1.0);
|
||||
|
||||
result.RawScore = normalizedScore;
|
||||
result.RuleBoost = ruleBoost;
|
||||
result.FinalScore = finalScore;
|
||||
|
||||
// ─── 3. 分类判定 ───
|
||||
result.Classification = finalScore switch
|
||||
{
|
||||
>= HighRiskThreshold => MalwareClass.Malicious,
|
||||
>= SuspiciousThreshold => MalwareClass.Suspicious,
|
||||
_ => MalwareClass.Benign
|
||||
};
|
||||
|
||||
// ─── 4. 恶意家族推测 ───
|
||||
result.SuspectedFamily = InferMalwareFamily(features);
|
||||
|
||||
// ─── 5. 关键特征贡献 ───
|
||||
result.TopContributors = topContributors
|
||||
.OrderByDescending(c => Math.Abs(c.contribution))
|
||||
.Take(5)
|
||||
.Select(c => (GetFeatureName(c.index), c.contribution))
|
||||
.ToList();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 决策树规则 —— 模拟关键分支逻辑
|
||||
/// </summary>
|
||||
private static double ApplyDecisionRules(MemoryFeatures f)
|
||||
{
|
||||
double boost = 0;
|
||||
|
||||
// 规则1: 系统进程 + RWX私有页 = 极高风险 (代码注入核心特征)
|
||||
if (f.IsSystemProcessName && f.RwxPrivatePages > 0)
|
||||
boost += 0.40;
|
||||
|
||||
// 规则2: 未签名 + 临时路径 + RWX = 高风险
|
||||
if (!f.IsSigned && f.IsTempPath && f.RwxPrivatePages > 0)
|
||||
boost += 0.30;
|
||||
|
||||
// 规则3: Process Hollowing = 立即高风险
|
||||
if (f.PeMismatchDisk)
|
||||
boost += 0.50;
|
||||
|
||||
// 规则4: 高熵 + 无磁盘映射可执行页 = 加密载荷
|
||||
if (f.MaxEntropy > 7.0 && f.AnonymousExecPages > 0 && f.ImagePages == 0)
|
||||
boost += 0.35;
|
||||
|
||||
// 规则5: 签名匹配 + 网络C2端口 = 远控木马
|
||||
if (f.HighSeverityMatches > 0 && f.SuspiciousPortConnections > 0)
|
||||
boost += 0.35;
|
||||
|
||||
// 规则6: 系统进程名 + 非系统路径 = 进程伪装
|
||||
if (f.IsSystemProcessName && !f.IsSystemPath)
|
||||
boost += 0.25;
|
||||
|
||||
// 规则7: 可疑API多 + 网络连接 = 可能C2
|
||||
if (f.SuspiciousApiStrings.Count >= 3 && f.ConnectionCount > 0)
|
||||
boost += 0.20;
|
||||
|
||||
// 规则8: 境外连接 + 非标准端口 + 高熵 = 数据外泄
|
||||
if (f.ForeignConnections > 0 && f.NonStandardPortConnections > 0 && f.MaxEntropy > 7.0)
|
||||
boost += 0.25;
|
||||
|
||||
// 规则9: 无磁盘模块 + 临时路径模块
|
||||
if (f.UnmappedModules > 0 && f.TempPathModules > 0)
|
||||
boost += 0.20;
|
||||
|
||||
return Math.Min(boost, 0.60);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 推测恶意软件家族
|
||||
/// </summary>
|
||||
private static string InferMalwareFamily(MemoryFeatures features)
|
||||
{
|
||||
var indicators = new List<string>();
|
||||
|
||||
// CobaltStrike
|
||||
if (features.SuspiciousStrings.Any(s => s.Contains("beacon", StringComparison.OrdinalIgnoreCase)))
|
||||
indicators.Add("CobaltStrike Beacon");
|
||||
|
||||
// Meterpreter
|
||||
if (features.SuspiciousStrings.Any(s => s.Contains("meterpreter", StringComparison.OrdinalIgnoreCase)))
|
||||
indicators.Add("Metasploit Meterpreter");
|
||||
|
||||
// Mimikatz
|
||||
if (features.SuspiciousStrings.Any(s => s.Contains("mimikatz", StringComparison.OrdinalIgnoreCase)) ||
|
||||
features.SuspiciousApiStrings.Any(a => a.Contains("MiniDump", StringComparison.OrdinalIgnoreCase)))
|
||||
indicators.Add("凭证窃取工具 (Mimikatz)");
|
||||
|
||||
// Process Hollowing
|
||||
if (features.PeMismatchDisk)
|
||||
indicators.Add("Process Hollowing");
|
||||
|
||||
// Ransomware
|
||||
if (features.SuspiciousStrings.Any(s => s.Contains("ENCRYPTED", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Contains("ransom", StringComparison.OrdinalIgnoreCase)))
|
||||
indicators.Add("勒索软件");
|
||||
|
||||
// Crypto Miner
|
||||
if (features.SuspiciousStrings.Any(s => s.Contains("stratum", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Contains("cryptonight", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Contains("coinmine", StringComparison.OrdinalIgnoreCase)))
|
||||
indicators.Add("挖矿木马");
|
||||
|
||||
// RAT
|
||||
if (features.SuspiciousApiStrings.Count >= 3 && features.ForeignConnections > 0 &&
|
||||
features.SuspiciousPortConnections > 0)
|
||||
indicators.Add("远控木马 (RAT)");
|
||||
|
||||
// Reflective DLL Injection
|
||||
if (features.AnonymousExecPages > 0 && features.RwxPrivatePages > 0 &&
|
||||
features.SuspiciousApiStrings.Any(a => a.Contains("Reflective", StringComparison.OrdinalIgnoreCase)))
|
||||
indicators.Add("反射DLL注入木马");
|
||||
|
||||
// Bootkit/Rootkit
|
||||
if (features.IsSystemProcessName && !features.IsSystemPath && features.RwxPrivatePages > 0)
|
||||
indicators.Add("Rootkit/伪装系统进程");
|
||||
|
||||
return indicators.Count > 0 ? string.Join(" | ", indicators) : "未知恶意家族";
|
||||
}
|
||||
|
||||
private static string GetFeatureName(int index) => index switch
|
||||
{
|
||||
0 => "未签名", 1 => "非系统路径", 2 => "临时路径",
|
||||
3 => "内存占用", 4 => "线程数", 5 => "系统进程名", 6 => "父进程异常",
|
||||
12 => "RWX页比例", 13 => "匿名可执行页", 14 => "RWX私有页",
|
||||
18 => "内存PE头", 19 => "PE与磁盘不一致",
|
||||
20 => "可疑API", 21 => "敏感字符串", 22 => "签名匹配", 23 => "高危签名",
|
||||
24 => "高熵代码段", 25 => "最大熵值", 26 => "高熵区域数",
|
||||
29 => "非标准端口", 30 => "可疑端口", 31 => "境外连接",
|
||||
35 => "无磁盘模块", 36 => "临时目录模块",
|
||||
_ => $"特征{index}"
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分类引擎结果
|
||||
/// </summary>
|
||||
public class ClassificationResult
|
||||
{
|
||||
public MalwareClass Classification { get; set; } = MalwareClass.Benign;
|
||||
public double RawScore { get; set; }
|
||||
public double RuleBoost { get; set; }
|
||||
public double FinalScore { get; set; }
|
||||
public string SuspectedFamily { get; set; } = "";
|
||||
public List<(string featureName, double contribution)> TopContributors { get; set; } = new();
|
||||
}
|
||||
|
||||
public enum MalwareClass
|
||||
{
|
||||
Benign,
|
||||
Suspicious,
|
||||
Malicious
|
||||
}
|
||||
279
src-csharp/MemScanEDR/Services/DetectionPipeline.cs
Normal file
279
src-csharp/MemScanEDR/Services/DetectionPipeline.cs
Normal file
@ -0,0 +1,279 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using MemScanEDR.Models;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 三级联动检测管线 —— 2.0 核心编排引擎
|
||||
///
|
||||
/// 流程:
|
||||
/// [快速预过滤] → [第一层: 基线异常检测] → [第二层: 监督分类] → [第三层: MITRE规则复核] → 输出
|
||||
///
|
||||
/// 速度优化:
|
||||
/// - 预过滤阶段不读内存,仅元数据判断,跳过 >80% 的进程
|
||||
/// - 只在可疑进程上执行完整特征提取
|
||||
/// - 基线匹配失败时提前退出,节省计算
|
||||
/// </summary>
|
||||
public class DetectionPipeline
|
||||
{
|
||||
private readonly BaselineEngine _baselineEngine;
|
||||
private readonly ClassificationEngine _classificationEngine;
|
||||
private readonly RuleEngine _ruleEngine;
|
||||
private readonly FeatureExtractor _featureExtractor;
|
||||
private readonly MemoryScanner _memoryScanner;
|
||||
private readonly BaselineLibrary _baselineLibrary;
|
||||
|
||||
// 扫描模式
|
||||
private readonly ScanMode _mode;
|
||||
|
||||
public DetectionPipeline(ScanMode mode = ScanMode.Balanced)
|
||||
{
|
||||
_mode = mode;
|
||||
_baselineLibrary = BaselineLibrary.CreateDefault();
|
||||
_baselineEngine = new BaselineEngine(_baselineLibrary);
|
||||
_classificationEngine = new ClassificationEngine();
|
||||
_ruleEngine = new RuleEngine();
|
||||
_featureExtractor = new FeatureExtractor();
|
||||
_memoryScanner = new MemoryScanner();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完整三级检测流程
|
||||
/// </summary>
|
||||
public Task<ScanResult> AnalyzeAsync(ProcessInfo proc, List<Signature> signatures)
|
||||
{
|
||||
return Task.FromResult(Analyze(proc, signatures));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 完整三级检测流程(同步核心)
|
||||
/// </summary>
|
||||
private ScanResult Analyze(ProcessInfo proc, List<Signature> signatures)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
// ─── 阶段0: 快速预过滤(不读内存)───
|
||||
var memoryRegions = _memoryScanner.GetMemoryRegions(proc.Pid);
|
||||
proc.MemoryRegions = memoryRegions;
|
||||
var signatureMatches = new List<SignatureMatch>();
|
||||
|
||||
var preFilterResult = QuickPreFilter(proc, memoryRegions);
|
||||
if (preFilterResult.canSkip)
|
||||
{
|
||||
return new ScanResult
|
||||
{
|
||||
Pid = proc.Pid,
|
||||
ProcessName = proc.Name,
|
||||
RiskLevel = RiskLevel.Safe,
|
||||
RiskScore = preFilterResult.score,
|
||||
Indicators = new List<string>(),
|
||||
Reasoning = preFilterResult.reason,
|
||||
AIProvider = "pipeline-v2/fast-filter",
|
||||
DetectionLayer = "PreFilter",
|
||||
ScanTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
// ─── 阶段1: 内存区域扫描(轻量)───
|
||||
if (_mode != ScanMode.Fast)
|
||||
{
|
||||
var suspiciousRegions = _memoryScanner.GetSuspiciousRegions(proc.Pid);
|
||||
if (suspiciousRegions.Count > 0 || proc.IsTempPath || !proc.IsSigned)
|
||||
{
|
||||
signatureMatches = _memoryScanner.ScanProcess(proc.Pid, signatures);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 阶段2: 特征提取 ───
|
||||
MemoryFeatures features;
|
||||
if (_mode == ScanMode.Fast)
|
||||
{
|
||||
features = _featureExtractor.QuickExtract(proc, memoryRegions);
|
||||
}
|
||||
else
|
||||
{
|
||||
features = _featureExtractor.Extract(proc, memoryRegions, signatureMatches, signatures);
|
||||
}
|
||||
|
||||
// ─── 第一层: 无监督基线检测 ───
|
||||
var baselineResult = _baselineEngine.Analyze(features);
|
||||
|
||||
// 完全匹配基线 → 直接判定为正常
|
||||
if (baselineResult.Classification == BaselineClassification.Normal &&
|
||||
baselineResult.AnomalyScore < 0.20)
|
||||
{
|
||||
return BuildSafeResult(proc, features, baselineResult, signatureMatches,
|
||||
$"第一层 | 进程内存结构完全匹配正常基线 [{baselineResult.BaselineName}] | 异常分数={baselineResult.AnomalyScore:F3}");
|
||||
}
|
||||
|
||||
// ─── 第二层: 监督分类 ───
|
||||
var classificationResult = _classificationEngine.Classify(features);
|
||||
|
||||
// 分类为良性 + 基线无异常 → 安全
|
||||
if (classificationResult.Classification == MalwareClass.Benign &&
|
||||
baselineResult.AnomalyScore < 0.40)
|
||||
{
|
||||
return BuildSafeResult(proc, features, baselineResult, signatureMatches,
|
||||
$"第二层 | 分类器判定良性 | {classificationResult.SuspectedFamily}");
|
||||
}
|
||||
|
||||
// ─── 第三层: MITRE ATT&CK规则复核 ───
|
||||
var ruleResult = _ruleEngine.Validate(features, classificationResult, baselineResult);
|
||||
|
||||
// ─── 构建最终结果 ───
|
||||
var indicators = BuildIndicators(features, baselineResult, classificationResult, ruleResult);
|
||||
var layer = DetermineDetectionLayer(baselineResult, classificationResult, ruleResult);
|
||||
|
||||
return new ScanResult
|
||||
{
|
||||
Pid = proc.Pid,
|
||||
ProcessName = proc.Name,
|
||||
RiskLevel = ruleResult.FinalRiskLevel,
|
||||
RiskScore = ruleResult.CombinedScore,
|
||||
Indicators = indicators,
|
||||
Reasoning = ruleResult.Reasoning,
|
||||
Matches = signatureMatches,
|
||||
AIProvider = $"pipeline-v2/{_mode.ToString().ToLower()}",
|
||||
DetectionLayer = layer,
|
||||
ScanTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
// ─── 快速预过滤 ───
|
||||
private static (bool canSkip, double score, string reason) QuickPreFilter(
|
||||
ProcessInfo proc, List<MemoryRegion> regions)
|
||||
{
|
||||
double score = 0;
|
||||
var reasons = new List<string>();
|
||||
|
||||
// 已签名 + 系统路径 + 无网络 → 几乎肯定安全
|
||||
if (proc.IsSigned && proc.IsSystemPath && proc.Connections.Count == 0)
|
||||
{
|
||||
// 再检查是否有可疑内存区域
|
||||
var hasRwx = regions.Any(r =>
|
||||
r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE");
|
||||
|
||||
if (!hasRwx && !proc.IsTempPath)
|
||||
{
|
||||
return (true, 0, $"预过滤 | 已签名系统程序 | PID={proc.Pid} {proc.Name}");
|
||||
}
|
||||
|
||||
// 脚本引擎/Shell 有合法RWX(JIT),即使有RWX也是正常的
|
||||
if (hasRwx && FeatureExtractor.IsScriptingEngine(proc))
|
||||
{
|
||||
return (true, 0.05, $"预过滤 | 已签名脚本引擎(RWX为JIT正常行为) | PID={proc.Pid} {proc.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
// 未签名 + 临时路径 → 需要进一步分析
|
||||
if (!proc.IsSigned && proc.IsTempPath)
|
||||
score += 0.30;
|
||||
|
||||
// 系统进程名 + 非系统路径 → 可疑
|
||||
if (FeatureExtractor.IsSystemProcessName(proc.Name) && !proc.IsSystemPath)
|
||||
score += 0.35;
|
||||
|
||||
// 网络连接到可疑端口
|
||||
if (proc.Connections.Any(c => IsSuspiciousPort(c.RemotePort)))
|
||||
score += 0.25;
|
||||
|
||||
// RWX 私有页
|
||||
if (regions.Any(r => r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE"))
|
||||
score += 0.20;
|
||||
|
||||
if (score < 0.05)
|
||||
return (true, 0, $"预过滤 | 无可疑特征 | PID={proc.Pid} {proc.Name}");
|
||||
|
||||
if (score < 0.15 && proc.IsSigned)
|
||||
return (true, score * 0.5, $"预过滤 | 已签名低风险 | PID={proc.Pid} {proc.Name} | score={score:F2}");
|
||||
|
||||
return (false, score, $"预过滤 | 进入AI分析 | PID={proc.Pid} {proc.Name} | score={score:F2}");
|
||||
}
|
||||
|
||||
private static bool IsSuspiciousPort(int port) => port switch
|
||||
{
|
||||
4444 or 5555 or 6666 or 7777 or 1337 or 31337 or 9999 or 12345 or 27015 => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
private static ScanResult BuildSafeResult(ProcessInfo proc, MemoryFeatures features,
|
||||
BaselineResult baseline, List<SignatureMatch> matches, string reasoning)
|
||||
{
|
||||
return new ScanResult
|
||||
{
|
||||
Pid = proc.Pid,
|
||||
ProcessName = proc.Name,
|
||||
RiskLevel = RiskLevel.Safe,
|
||||
RiskScore = Math.Min(baseline.AnomalyScore, 0.15),
|
||||
Indicators = new List<string>(),
|
||||
Reasoning = reasoning,
|
||||
Matches = matches,
|
||||
AIProvider = "pipeline-v2/baseline",
|
||||
DetectionLayer = "Layer1_Baseline",
|
||||
ScanTime = DateTime.Now
|
||||
};
|
||||
}
|
||||
|
||||
private static List<string> BuildIndicators(MemoryFeatures f, BaselineResult baseline,
|
||||
ClassificationResult classification, RuleValidationResult rule)
|
||||
{
|
||||
var indicators = new List<string>();
|
||||
|
||||
if (baseline.IsAnomalous)
|
||||
{
|
||||
indicators.Add($"基线异常(分数={baseline.AnomalyScore:F2})");
|
||||
foreach (var idx in baseline.DeviantFeatureIndices.Take(3))
|
||||
indicators.Add($"偏离特征#{idx}");
|
||||
}
|
||||
|
||||
if (classification.Classification == MalwareClass.Malicious)
|
||||
indicators.Add($"AI分类:恶意({classification.SuspectedFamily})");
|
||||
else if (classification.Classification == MalwareClass.Suspicious)
|
||||
indicators.Add("AI分类:可疑");
|
||||
|
||||
if (rule.MatchedRuleCount > 0)
|
||||
{
|
||||
foreach (var tech in rule.MatchedTechniques.Take(5))
|
||||
indicators.Add(tech);
|
||||
}
|
||||
|
||||
if (f.RwxPrivatePages > 0)
|
||||
indicators.Add($"RWX私有页={f.RwxPrivatePages}");
|
||||
|
||||
if (f.PeMismatchDisk)
|
||||
indicators.Add("ProcessHollowing");
|
||||
|
||||
if (f.SuspiciousStringHits > 0)
|
||||
indicators.Add($"敏感字符串={f.SuspiciousStringHits}");
|
||||
|
||||
return indicators;
|
||||
}
|
||||
|
||||
private static string DetermineDetectionLayer(BaselineResult baseline,
|
||||
ClassificationResult classification, RuleValidationResult rule)
|
||||
{
|
||||
if (rule.MatchedRuleCount >= 2) return "L3_Rules+L2_AI+L1_Baseline";
|
||||
if (classification.Classification == MalwareClass.Malicious) return "L2_AI+L1_Baseline";
|
||||
if (baseline.IsAnomalous) return "L1_Baseline";
|
||||
return "PreFilter";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扫描模式
|
||||
/// </summary>
|
||||
public enum ScanMode
|
||||
{
|
||||
/// <summary>快速模式:仅元数据,不读取内存</summary>
|
||||
Fast,
|
||||
|
||||
/// <summary>平衡模式:采样熵值 + 特征提取</summary>
|
||||
Balanced,
|
||||
|
||||
/// <summary>深度模式:全量内存扫描 + 完整特征提取</summary>
|
||||
Deep
|
||||
}
|
||||
145
src-csharp/MemScanEDR/Services/EntropyAnalyzer.cs
Normal file
145
src-csharp/MemScanEDR/Services/EntropyAnalyzer.cs
Normal file
@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using MemScanEDR.Models;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 熵值分析器 —— 计算内存区域的 Shannon 熵
|
||||
/// 高熵 (>7.0) 通常表示加密/压缩载荷,是恶意软件的重要指标
|
||||
/// 正常程序代码段熵值通常稳定在 3.0-6.5 范围
|
||||
/// </summary>
|
||||
public class EntropyAnalyzer
|
||||
{
|
||||
// Entropy calculation constants
|
||||
private const int SampleSize = 4096; // 默认采样大小
|
||||
private const int MaxSamplesPerRegion = 8; // 每个区域最多采样点
|
||||
private const double HighEntropyThreshold = 7.0;
|
||||
|
||||
private const uint PROCESS_VM_READ = 0x0010;
|
||||
|
||||
/// <summary>
|
||||
/// 计算单个内存区域的熵值(通过采样子区域)
|
||||
/// </summary>
|
||||
public double CalculateRegionEntropy(IntPtr hProcess, ulong baseAddress, ulong regionSize)
|
||||
{
|
||||
if (regionSize == 0) return 0;
|
||||
|
||||
var entropies = new List<double>();
|
||||
var sampleCount = Math.Min(MaxSamplesPerRegion, (int)(regionSize / SampleSize));
|
||||
if (sampleCount < 1) sampleCount = 1;
|
||||
|
||||
for (int i = 0; i < sampleCount; i++)
|
||||
{
|
||||
var offset = (ulong)(i * (long)(regionSize / (ulong)sampleCount));
|
||||
var readSize = Math.Min(SampleSize, regionSize - offset);
|
||||
if (readSize > int.MaxValue) readSize = SampleSize;
|
||||
|
||||
var buffer = new byte[readSize];
|
||||
if (ReadProcessMemory(hProcess, (IntPtr)(baseAddress + offset), buffer, (int)readSize, out _))
|
||||
{
|
||||
entropies.Add(CalculateShannonEntropy(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
return entropies.Count > 0 ? entropies.Average() : 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量计算所有内存区域的熵值,更新 MemoryRegion.Entropy 字段
|
||||
/// </summary>
|
||||
public EntropyResult AnalyzeRegions(IntPtr hProcess, List<MemoryRegion> regions)
|
||||
{
|
||||
var result = new EntropyResult();
|
||||
var allEntropies = new List<double>();
|
||||
|
||||
foreach (var region in regions)
|
||||
{
|
||||
// 只对已提交的可读区域计算熵
|
||||
if (region.State != "COMMIT") continue;
|
||||
if (region.Protect == "NOACCESS" || region.Protect == "0x01") continue;
|
||||
|
||||
region.Entropy = CalculateRegionEntropy(hProcess, region.BaseAddress, region.Size);
|
||||
allEntropies.Add(region.Entropy);
|
||||
|
||||
if (region.Entropy > HighEntropyThreshold)
|
||||
{
|
||||
result.HighEntropyRegions++;
|
||||
result.HighEntropyTotalSize += region.Size;
|
||||
}
|
||||
}
|
||||
|
||||
if (allEntropies.Count > 0)
|
||||
{
|
||||
result.AvgEntropy = allEntropies.Average();
|
||||
result.MaxEntropy = allEntropies.Max();
|
||||
result.EntropyStdDev = CalculateStdDev(allEntropies, result.AvgEntropy);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快速估算内存区域熵值(单采样点,用于快速扫描模式)
|
||||
/// </summary>
|
||||
public double QuickEntropyEstimate(IntPtr hProcess, ulong baseAddress, ulong regionSize)
|
||||
{
|
||||
// 只采样区域中间的 256 字节
|
||||
var sampleSize = 256u;
|
||||
var offset = regionSize / 2;
|
||||
if (offset + sampleSize > regionSize) offset = 0;
|
||||
|
||||
var buffer = new byte[sampleSize];
|
||||
if (ReadProcessMemory(hProcess, (IntPtr)(baseAddress + offset), buffer, (int)Math.Min(sampleSize, regionSize), out _))
|
||||
{
|
||||
return CalculateShannonEntropy(buffer);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shannon 熵计算
|
||||
/// </summary>
|
||||
public static double CalculateShannonEntropy(byte[] data)
|
||||
{
|
||||
if (data == null || data.Length == 0) return 0;
|
||||
|
||||
var counts = new int[256];
|
||||
foreach (var b in data)
|
||||
counts[b]++;
|
||||
|
||||
double entropy = 0;
|
||||
double len = data.Length;
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
if (counts[i] == 0) continue;
|
||||
var p = counts[i] / len;
|
||||
entropy -= p * Math.Log2(p);
|
||||
}
|
||||
|
||||
return entropy; // 范围 [0, 8]
|
||||
}
|
||||
|
||||
private static double CalculateStdDev(List<double> values, double mean)
|
||||
{
|
||||
if (values.Count <= 1) return 0;
|
||||
var sumSq = values.Sum(v => Math.Pow(v - mean, 2));
|
||||
return Math.Sqrt(sumSq / (values.Count - 1));
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
|
||||
byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 熵分析结果
|
||||
/// </summary>
|
||||
public class EntropyResult
|
||||
{
|
||||
public double AvgEntropy { get; set; }
|
||||
public double MaxEntropy { get; set; }
|
||||
public double EntropyStdDev { get; set; }
|
||||
public int HighEntropyRegions { get; set; }
|
||||
public ulong HighEntropyTotalSize { get; set; }
|
||||
}
|
||||
331
src-csharp/MemScanEDR/Services/FeatureExtractor.cs
Normal file
331
src-csharp/MemScanEDR/Services/FeatureExtractor.cs
Normal file
@ -0,0 +1,331 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using MemScanEDR.Models;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 特征提取器 —— 核心模块
|
||||
/// 从进程内存中提取全部多维特征,作为 AI 模型的输入向量
|
||||
/// 包含:元数据、页权限分布、代码特征、熵值、网络行为、进程树关系
|
||||
/// </summary>
|
||||
public class FeatureExtractor
|
||||
{
|
||||
private readonly EntropyAnalyzer _entropyAnalyzer = new();
|
||||
private readonly PEHeaderAnalyzer _peAnalyzer = new();
|
||||
|
||||
private const uint PROCESS_VM_READ = 0x0010;
|
||||
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
|
||||
|
||||
/// <summary>
|
||||
/// 从进程信息中提取完整的 MemoryFeatures 特征向量
|
||||
/// </summary>
|
||||
public MemoryFeatures Extract(ProcessInfo proc, List<MemoryRegion> regions,
|
||||
List<SignatureMatch> matches, List<Signature> signatures)
|
||||
{
|
||||
var features = new MemoryFeatures();
|
||||
|
||||
// ─── 一、进程元数据 ───
|
||||
features.ProcessName = proc.Name;
|
||||
features.ExePath = proc.ExePath;
|
||||
features.ParentPid = proc.Ppid;
|
||||
features.ParentName = proc.ParentName;
|
||||
features.CmdLine = proc.CmdLine;
|
||||
features.StartTime = proc.StartTime;
|
||||
features.WorkingSetMB = proc.WorkingSetMB;
|
||||
features.ThreadCount = proc.ThreadCount;
|
||||
features.IsSigned = proc.IsSigned;
|
||||
features.Signer = proc.Signer;
|
||||
features.IsSystemPath = proc.IsSystemPath;
|
||||
features.IsTempPath = proc.IsTempPath;
|
||||
|
||||
// ─── 二、内存页权限分布 ───
|
||||
ExtractPagePermissions(features, regions);
|
||||
|
||||
// ─── 三、内存代码特征 ───
|
||||
ExtractCodeFeatures(features, proc, regions, matches);
|
||||
|
||||
// ─── 四、熵值特征 ───
|
||||
ExtractEntropyFeatures(features, proc.Pid, regions);
|
||||
|
||||
// ─── 五、网络行为特征 ───
|
||||
ExtractNetworkFeatures(features, proc.Connections);
|
||||
|
||||
// ─── 六、进程树关系 ───
|
||||
features.IsSystemProcessName = IsSystemProcessName(proc.Name);
|
||||
features.HasLegitimateParent = HasLegitimateParent(proc.ParentName, proc.Name);
|
||||
|
||||
// ─── 七、DLL 加载特征 ───
|
||||
features.LoadedModuleCount = proc.LoadedModules.Count;
|
||||
features.UnmappedModules = CountUnmappedModules(regions);
|
||||
features.TempPathModules = CountTempModules(proc.LoadedModules);
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 快速特征提取 —— 仅提取元数据和权限分布 (不读内存,速度最快)
|
||||
/// 用于快速预过滤阶段
|
||||
/// </summary>
|
||||
public MemoryFeatures QuickExtract(ProcessInfo proc, List<MemoryRegion> regions)
|
||||
{
|
||||
var features = new MemoryFeatures
|
||||
{
|
||||
ProcessName = proc.Name,
|
||||
ExePath = proc.ExePath,
|
||||
ParentPid = proc.Ppid,
|
||||
ParentName = proc.ParentName,
|
||||
IsSigned = proc.IsSigned,
|
||||
IsSystemPath = proc.IsSystemPath,
|
||||
IsTempPath = proc.IsTempPath,
|
||||
WorkingSetMB = proc.WorkingSetMB,
|
||||
ThreadCount = proc.ThreadCount,
|
||||
IsSystemProcessName = IsSystemProcessName(proc.Name),
|
||||
HasLegitimateParent = HasLegitimateParent(proc.ParentName, proc.Name)
|
||||
};
|
||||
|
||||
ExtractPagePermissions(features, regions);
|
||||
ExtractNetworkFeatures(features, proc.Connections);
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
// ─── 内存页权限提取 ───
|
||||
private static void ExtractPagePermissions(MemoryFeatures f, List<MemoryRegion> regions)
|
||||
{
|
||||
f.TotalRegions = regions.Count;
|
||||
|
||||
foreach (var r in regions)
|
||||
{
|
||||
switch (r.Protect)
|
||||
{
|
||||
case "READONLY": f.ReadOnlyPages++; break;
|
||||
case "READWRITE": f.ReadWritePages++; break;
|
||||
case "EXECUTE": case "EXECUTE_READ": f.ExecuteReadPages++; break;
|
||||
case "EXECUTE_READWRITE": f.ExecuteReadWritePages++; break;
|
||||
}
|
||||
|
||||
switch (r.Type)
|
||||
{
|
||||
case "PRIVATE": f.PrivatePages++; break;
|
||||
case "MAPPED": f.MappedPages++; break;
|
||||
case "IMAGE": f.ImagePages++; break;
|
||||
}
|
||||
|
||||
// 无文件匿名可执行内存
|
||||
if (r.Type != "IMAGE" && (r.Protect.Contains("EXECUTE")))
|
||||
{
|
||||
f.AnonymousExecPages++;
|
||||
}
|
||||
|
||||
// RWX 私有页 —— 核心恶意指标
|
||||
if (r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE")
|
||||
{
|
||||
f.RwxPrivatePages++;
|
||||
f.RwxTotalSize += r.Size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 代码特征提取 ───
|
||||
private void ExtractCodeFeatures(MemoryFeatures f, ProcessInfo proc,
|
||||
List<MemoryRegion> regions, List<SignatureMatch> matches)
|
||||
{
|
||||
// 脚本引擎/运行时自身是合法进程,跳过内存字符串扫描避免误报
|
||||
if (IsScriptingEngine(proc))
|
||||
{
|
||||
f.SignatureMatchCount = matches.Count;
|
||||
f.HighSeverityMatches = matches.Count(m => m.Severity == "high");
|
||||
return;
|
||||
}
|
||||
|
||||
// 打开进程读取内存
|
||||
var hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, proc.Pid);
|
||||
if (hProcess == IntPtr.Zero) return;
|
||||
|
||||
try
|
||||
{
|
||||
// PE 头检测
|
||||
foreach (var region in regions.Where(r => r.Type == "IMAGE" && r.State == "COMMIT"))
|
||||
{
|
||||
if (_peAnalyzer.HasInMemoryPE(hProcess, region.BaseAddress))
|
||||
{
|
||||
f.HasInMemoryPE = true;
|
||||
|
||||
// 检查内存PE与磁盘是否一致
|
||||
if (!string.IsNullOrEmpty(proc.ExePath))
|
||||
{
|
||||
var integrity = _peAnalyzer.CheckPeIntegrity(proc.ExePath, hProcess, region.BaseAddress);
|
||||
f.PeMismatchDisk = !integrity.IsValid;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 敏感字符串提取 (只在可疑区域提取以提升速度)
|
||||
var suspiciousRegions = regions.Where(r =>
|
||||
r.Type == "PRIVATE" && r.Protect.Contains("EXECUTE") && r.Size < 10 * 1024 * 1024
|
||||
).Take(5);
|
||||
|
||||
foreach (var region in suspiciousRegions)
|
||||
{
|
||||
var strings = _peAnalyzer.ExtractSuspiciousStrings(hProcess, region.BaseAddress, region.Size);
|
||||
f.SuspiciousStrings.AddRange(strings.Take(5));
|
||||
f.SuspiciousStringHits += strings.Count;
|
||||
|
||||
var apis = _peAnalyzer.ExtractSuspiciousApis(hProcess, region.BaseAddress, Math.Min(region.Size, 32768));
|
||||
f.SuspiciousApiStrings.AddRange(apis);
|
||||
}
|
||||
|
||||
// 去重
|
||||
f.SuspiciousStrings = f.SuspiciousStrings.Distinct().ToList();
|
||||
f.SuspiciousApiStrings = f.SuspiciousApiStrings.Distinct().ToList();
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
|
||||
// 签名匹配
|
||||
f.SignatureMatchCount = matches.Count;
|
||||
f.HighSeverityMatches = matches.Count(m => m.Severity == "high");
|
||||
}
|
||||
|
||||
// ─── 熵值特征提取 ───
|
||||
private void ExtractEntropyFeatures(MemoryFeatures f, int pid, List<MemoryRegion> regions)
|
||||
{
|
||||
var hProcess = OpenProcess(PROCESS_VM_READ, false, pid);
|
||||
if (hProcess == IntPtr.Zero) return;
|
||||
|
||||
try
|
||||
{
|
||||
var result = _entropyAnalyzer.AnalyzeRegions(hProcess, regions);
|
||||
f.AvgCodeEntropy = result.AvgEntropy;
|
||||
f.MaxEntropy = result.MaxEntropy;
|
||||
f.HighEntropyRegions = result.HighEntropyRegions;
|
||||
f.HighEntropyTotalSize = result.HighEntropyTotalSize;
|
||||
f.EntropyStdDev = result.EntropyStdDev;
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 网络特征提取 ───
|
||||
private static void ExtractNetworkFeatures(MemoryFeatures f, List<NetworkConnection> connections)
|
||||
{
|
||||
f.ConnectionCount = connections.Count;
|
||||
|
||||
foreach (var conn in connections)
|
||||
{
|
||||
if (conn.State == "Listen")
|
||||
f.ListeningPorts++;
|
||||
|
||||
// 非标准端口
|
||||
if (conn.RemotePort > 0 && conn.RemotePort != 80 && conn.RemotePort != 443
|
||||
&& conn.RemotePort != 8080 && conn.RemotePort != 8443)
|
||||
{
|
||||
f.NonStandardPortConnections++;
|
||||
}
|
||||
|
||||
// 已知C2端口
|
||||
if (IsSuspiciousPort(conn.RemotePort))
|
||||
f.SuspiciousPortConnections++;
|
||||
|
||||
// 境外连接检测 (简化判断)
|
||||
if (IsForeignAddress(conn.RemoteAddr))
|
||||
f.ForeignConnections++;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsSuspiciousPort(int port) => port switch
|
||||
{
|
||||
4444 or 5555 or 6666 or 7777 or 1337 or 31337 or 9999 or 12345 or 27015 => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
private static bool IsForeignAddress(string addr)
|
||||
{
|
||||
if (string.IsNullOrEmpty(addr)) return false;
|
||||
// 私有地址范围
|
||||
var ip = addr.Split(':')[0];
|
||||
return !(ip.StartsWith("10.") || ip.StartsWith("172.16.") ||
|
||||
ip.StartsWith("192.168.") || ip == "127.0.0.1" || ip == "0.0.0.0");
|
||||
}
|
||||
|
||||
// ─── 进程树辅助方法 ───
|
||||
public static bool IsSystemProcessName(string name) =>
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"svchost.exe", "csrss.exe", "winlogon.exe", "services.exe",
|
||||
"lsass.exe", "spoolsv.exe", "smss.exe", "wininit.exe",
|
||||
"dwm.exe", "taskhostw.exe", "sihost.exe", "runtimebroker.exe"
|
||||
}.Contains(name);
|
||||
|
||||
public static bool HasLegitimateParent(string parentName, string childName)
|
||||
{
|
||||
// 正常父子关系
|
||||
if (string.IsNullOrEmpty(parentName)) return false;
|
||||
|
||||
var childLower = childName.ToLowerInvariant();
|
||||
var parentLower = parentName.ToLowerInvariant();
|
||||
|
||||
// 系统进程的正常父进程
|
||||
if (parentLower is "services.exe" or "wininit.exe" or "smss.exe")
|
||||
return true;
|
||||
|
||||
// Explorer 启动用户程序
|
||||
if (parentLower == "explorer.exe")
|
||||
return true;
|
||||
|
||||
// 浏览器多进程
|
||||
if ((parentLower.Contains("chrome") || parentLower.Contains("msedge") || parentLower.Contains("firefox"))
|
||||
&& (childLower.Contains("chrome") || childLower.Contains("msedge") || childLower.Contains("firefox")))
|
||||
return true;
|
||||
|
||||
// svchost 只由 services.exe 启动
|
||||
if (childLower == "svchost.exe" && parentLower == "services.exe")
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否为脚本引擎/Shell —— 这类进程自身包含大量"可疑"字符串
|
||||
/// 但属于正常功能(如 PowerShell 的 Invoke-Expression, IEX 等是合法的内置cmdlet)
|
||||
/// </summary>
|
||||
public static bool IsScriptingEngine(ProcessInfo proc)
|
||||
{
|
||||
if (!proc.IsSigned || !proc.IsSystemPath) return false;
|
||||
|
||||
var nameLower = proc.Name.ToLowerInvariant();
|
||||
return nameLower switch
|
||||
{
|
||||
"powershell.exe" or "pwsh.exe" or "powershell_ise.exe" => true,
|
||||
"cmd.exe" => true,
|
||||
"wscript.exe" or "cscript.exe" => true,
|
||||
"conhost.exe" => true,
|
||||
"python.exe" or "python3.exe" or "pythonw.exe" => true,
|
||||
"wsl.exe" or "bash.exe" => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
private static int CountUnmappedModules(List<MemoryRegion> regions) =>
|
||||
regions.Count(r => r.Type == "PRIVATE" && r.Protect.Contains("EXECUTE") && r.Size > 4096);
|
||||
|
||||
private static int CountTempModules(List<string> modules) =>
|
||||
modules.Count(m => m.Contains("\\Temp\\", StringComparison.OrdinalIgnoreCase) ||
|
||||
m.Contains("\\tmp\\", StringComparison.OrdinalIgnoreCase) ||
|
||||
m.Contains("\\AppData\\Local\\Temp\\", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// ─── P/Invoke ───
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
}
|
||||
@ -5,15 +5,24 @@ using MemScanEDR.Models;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 内存扫描服务 v2.0 —— 优化版
|
||||
/// 改进:
|
||||
/// - 单次 OpenProcess 遍历所有区域,避免重复打开进程句柄
|
||||
/// - 按需扫描:只对可疑区域做签名字节匹配,减少全量扫描
|
||||
/// - 缓存内存区域数据,供 FeatureExtractor 复用
|
||||
/// </summary>
|
||||
public class MemoryScanner
|
||||
{
|
||||
public List<Signature> LoadSignatures(string rulesDir)
|
||||
{
|
||||
var sigs = GetBuiltinSignatures();
|
||||
// TODO: Load YARA rules from rulesDir
|
||||
return sigs;
|
||||
return GetBuiltinSignatures();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扫描进程内存匹配签名 —— 优化版
|
||||
/// 仅对可疑区域(EXECUTE + PRIVATE)做签名字节匹配
|
||||
/// </summary>
|
||||
public List<SignatureMatch> ScanProcess(int pid, List<Signature> signatures)
|
||||
{
|
||||
var matches = new List<SignatureMatch>();
|
||||
@ -22,57 +31,115 @@ public class MemoryScanner
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var sig in signatures)
|
||||
var address = 0UL;
|
||||
var mbi = new MEMORY_BASIC_INFORMATION();
|
||||
var mbiSize = (uint)Marshal.SizeOf<MEMORY_BASIC_INFORMATION>();
|
||||
|
||||
while (VirtualQueryEx(hProcess, (IntPtr)address, out mbi, mbiSize) != 0)
|
||||
{
|
||||
var results = ScanMemory(hProcess, sig);
|
||||
matches.AddRange(results);
|
||||
if (mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS && mbi.Protect != PAGE_GUARD)
|
||||
{
|
||||
var regionSize = (ulong)mbi.RegionSize;
|
||||
if (regionSize > 0 && regionSize < 50 * 1024 * 1024) // 50MB 上限 (降低开销)
|
||||
{
|
||||
var protect = GetProtectString(mbi.Protect);
|
||||
var type = GetTypeString(mbi.Type);
|
||||
|
||||
// 仅对可疑组合做签名字节扫描
|
||||
bool isSuspicious = (protect.Contains("EXECUTE") && type == "PRIVATE") ||
|
||||
(protect.Contains("EXECUTE_READWRITE"));
|
||||
|
||||
if (isSuspicious)
|
||||
{
|
||||
foreach (var sig in signatures)
|
||||
{
|
||||
if (IsSignatureRelevant(sig, protect, type))
|
||||
{
|
||||
// v2.0: 实际读取内存做字节匹配
|
||||
var found = ScanRegionForSignature(hProcess, (ulong)mbi.BaseAddress, regionSize, sig);
|
||||
if (found)
|
||||
{
|
||||
matches.Add(new SignatureMatch
|
||||
{
|
||||
SigId = sig.SigId,
|
||||
Category = sig.Category,
|
||||
Severity = sig.Severity,
|
||||
Description = sig.Description,
|
||||
Address = $"0x{(ulong)mbi.BaseAddress:X}",
|
||||
Protect = protect
|
||||
});
|
||||
break; // 每区域每条签名只匹配一次
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
address = (ulong)mbi.BaseAddress + (ulong)mbi.RegionSize;
|
||||
if (address <= (ulong)mbi.BaseAddress) break;
|
||||
}
|
||||
}
|
||||
finally { CloseHandle(hProcess); }
|
||||
return matches;
|
||||
}
|
||||
|
||||
private List<SignatureMatch> ScanMemory(IntPtr hProcess, Signature sig)
|
||||
/// <summary>
|
||||
/// 在实际内存区域中搜索签名字节模式
|
||||
/// </summary>
|
||||
private static bool ScanRegionForSignature(IntPtr hProcess, ulong baseAddr, ulong regionSize, Signature sig)
|
||||
{
|
||||
var results = new List<SignatureMatch>();
|
||||
var address = 0UL;
|
||||
var mbi = new MEMORY_BASIC_INFORMATION();
|
||||
var mbiSize = (uint)Marshal.SizeOf<MEMORY_BASIC_INFORMATION>();
|
||||
if (string.IsNullOrEmpty(sig.Value)) return false;
|
||||
|
||||
while (VirtualQueryEx(hProcess, (IntPtr)address, out mbi, mbiSize) != 0)
|
||||
var searchBytes = System.Text.Encoding.ASCII.GetBytes(sig.Value);
|
||||
if (searchBytes.Length == 0) return false;
|
||||
|
||||
// 分块扫描,每次读 64KB
|
||||
const int chunkSize = 65536;
|
||||
var buffer = new byte[chunkSize + searchBytes.Length]; // 额外空间处理跨块匹配
|
||||
|
||||
for (ulong offset = 0; offset < regionSize; offset += chunkSize)
|
||||
{
|
||||
if (mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS && mbi.Protect != PAGE_GUARD)
|
||||
var readSize = (int)Math.Min((ulong)chunkSize, regionSize - offset);
|
||||
if (ReadProcessMemory(hProcess, (IntPtr)(baseAddr + offset), buffer, readSize, out _))
|
||||
{
|
||||
var regionSize = (ulong)mbi.RegionSize;
|
||||
if (regionSize > 0 && regionSize < 100 * 1024 * 1024) // max 100MB per region
|
||||
{
|
||||
var protect = GetProtectString(mbi.Protect);
|
||||
var type = GetTypeString(mbi.Type);
|
||||
|
||||
// Check for suspicious patterns
|
||||
if (sig.Category == "shellcode" || sig.Category == "c2" || sig.Category == "malware")
|
||||
{
|
||||
if (protect.Contains("EXECUTE") && type == "PRIVATE")
|
||||
{
|
||||
results.Add(new SignatureMatch
|
||||
{
|
||||
SigId = sig.SigId,
|
||||
Category = sig.Category,
|
||||
Severity = sig.Severity,
|
||||
Description = sig.Description,
|
||||
Address = $"0x{address:X}",
|
||||
Protect = protect
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ContainsPattern(buffer.AsSpan(0, readSize), searchBytes))
|
||||
return true;
|
||||
}
|
||||
address = (ulong)mbi.BaseAddress + (ulong)mbi.RegionSize;
|
||||
if (address <= (ulong)mbi.BaseAddress) break; // overflow protection
|
||||
}
|
||||
return results;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ContainsPattern(Span<byte> data, byte[] pattern)
|
||||
{
|
||||
if (pattern.Length == 0 || data.Length < pattern.Length) return false;
|
||||
for (int i = 0; i <= data.Length - pattern.Length; i++)
|
||||
{
|
||||
bool match = true;
|
||||
for (int j = 0; j < pattern.Length; j++)
|
||||
{
|
||||
if (data[i + j] != pattern[j]) { match = false; break; }
|
||||
}
|
||||
if (match) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断签名是否与当前内存区域相关
|
||||
/// </summary>
|
||||
private static bool IsSignatureRelevant(Signature sig, string protect, string type)
|
||||
{
|
||||
// shellcode/c2/malware 类签名仅对可执行私有内存有意义
|
||||
if (sig.Category is "shellcode" or "c2" or "malware" or "injection")
|
||||
{
|
||||
return protect.Contains("EXECUTE") && type == "PRIVATE";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取进程所有内存区域(供 FeatureExtractor 复用)
|
||||
/// </summary>
|
||||
public List<MemoryRegion> GetMemoryRegions(int pid)
|
||||
{
|
||||
var regions = new List<MemoryRegion>();
|
||||
@ -106,6 +173,9 @@ public class MemoryScanner
|
||||
return regions;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取可疑内存区域
|
||||
/// </summary>
|
||||
public List<MemoryRegion> GetSuspiciousRegions(int pid)
|
||||
{
|
||||
var regions = GetMemoryRegions(pid);
|
||||
@ -115,33 +185,29 @@ public class MemoryScanner
|
||||
).ToList();
|
||||
}
|
||||
|
||||
// ─── 保护/类型字符串转换 ───
|
||||
private static string GetProtectString(uint protect) => protect switch
|
||||
{
|
||||
0x02 => "READONLY",
|
||||
0x04 => "READWRITE",
|
||||
0x08 => "WRITECOPY",
|
||||
0x10 => "EXECUTE",
|
||||
0x20 => "EXECUTE_READ",
|
||||
0x40 => "EXECUTE_READWRITE",
|
||||
0x80 => "EXECUTE_WRITECOPY",
|
||||
_ => $"0x{protect:X2}"
|
||||
0x01 => "NOACCESS", 0x02 => "READONLY", 0x04 => "READWRITE",
|
||||
0x08 => "WRITECOPY", 0x10 => "EXECUTE", 0x20 => "EXECUTE_READ",
|
||||
0x40 => "EXECUTE_READWRITE", 0x80 => "EXECUTE_WRITECOPY",
|
||||
0x100 => "PAGE_GUARD", _ => $"0x{protect:X2}"
|
||||
};
|
||||
|
||||
private static string GetTypeString(uint type) => type switch
|
||||
{
|
||||
0x20000 => "PRIVATE",
|
||||
0x40000 => "MAPPED",
|
||||
0x1000000 => "IMAGE",
|
||||
0x20000 => "PRIVATE", 0x40000 => "MAPPED", 0x1000000 => "IMAGE",
|
||||
_ => $"0x{type:X}"
|
||||
};
|
||||
|
||||
// ─── 内置签名 ───
|
||||
public static List<Signature> GetBuiltinSignatures() => new()
|
||||
{
|
||||
new() { SigId = "SHELLCODE_NOP_SLED", Value = "\x90\x90\x90\x90", DataType = "string", Category = "shellcode", Severity = "medium", Description = "NOP sled detected" },
|
||||
new() { SigId = "SHELLCODE_GETEIP", Value = "\xE8\x00\x00\x00\x00\x58", DataType = "string", Category = "shellcode", Severity = "medium", Description = "CALL/POP EIP technique" },
|
||||
new() { SigId = "SHELLCODE_ROR13", Value = "\xC1\xC8\x0D", DataType = "string", Category = "shellcode", Severity = "medium", Description = "ROR13 API hash pattern" },
|
||||
new() { SigId = "SHELLCODE_NOP_SLED", Value = "\x90\x90\x90\x90", DataType = "bytes", Category = "shellcode", Severity = "medium", Description = "NOP sled detected" },
|
||||
new() { SigId = "SHELLCODE_GETEIP", Value = "\xE8\x00\x00\x00\x00\x58", DataType = "bytes", Category = "shellcode", Severity = "medium", Description = "CALL/POP EIP technique" },
|
||||
new() { SigId = "SHELLCODE_ROR13", Value = "\xC1\xC8\x0D", DataType = "bytes", Category = "shellcode", Severity = "medium", Description = "ROR13 API hash pattern" },
|
||||
new() { SigId = "C2_COBALTSTRIKE", Value = "beacon", DataType = "string", Category = "c2", Severity = "high", Description = "CobaltStrike Beacon string" },
|
||||
new() { SigId = "C2_METERPRETER", Value = "WS2_32.dll", DataType = "string", Category = "c2", Severity = "medium", Description = "Meterpreter WinSock" },
|
||||
new() { SigId = "C2_METERPRETER", Value = "WS2_32.dll", DataType = "string", Category = "c2", Severity = "medium", Description = "Meterpreter WinSock reference" },
|
||||
new() { SigId = "INJECTION_REFLECTIVE", Value = "ReflectiveLoader", DataType = "string", Category = "injection", Severity = "high", Description = "Reflective DLL injection" },
|
||||
new() { SigId = "INJECTION_CRT", Value = "CreateRemoteThread", DataType = "string", Category = "injection", Severity = "medium", Description = "Remote thread injection" },
|
||||
new() { SigId = "INJECTION_VAE", Value = "VirtualAllocEx", DataType = "string", Category = "injection", Severity = "medium", Description = "VirtualAllocEx injection" },
|
||||
@ -149,8 +215,12 @@ public class MemoryScanner
|
||||
new() { SigId = "CRYPTO_MINER", Value = "stratum+tcp://", DataType = "string", Category = "crypto", Severity = "high", Description = "Crypto mining pool" },
|
||||
new() { SigId = "RANSOM_NOTE", Value = "YOUR_FILES_ARE_ENCRYPTED", DataType = "string", Category = "malware", Severity = "high", Description = "Ransomware note" },
|
||||
new() { SigId = "PS_DOWNLOAD", Value = "IEX (New-Object", DataType = "string", Category = "malware", Severity = "medium", Description = "PowerShell download exec" },
|
||||
new() { SigId = "PROC_HOLLOW", Value = "NtUnmapViewOfSection", DataType = "string", Category = "injection", Severity = "high", Description = "Process hollowing NtUnmap" },
|
||||
new() { SigId = "SHELLCODE_URLDOWNLOAD", Value = "URLDownloadToFile", DataType = "string", Category = "shellcode", Severity = "medium", Description = "Shellcode URL download" },
|
||||
new() { SigId = "KEYLOGGER_API", Value = "GetAsyncKeyState", DataType = "string", Category = "malware", Severity = "medium", Description = "Keylogger API" },
|
||||
};
|
||||
|
||||
// ─── P/Invoke ───
|
||||
private const uint PROCESS_VM_READ = 0x0010;
|
||||
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
|
||||
private const uint MEM_COMMIT = 0x1000;
|
||||
@ -177,4 +247,8 @@ public class MemoryScanner
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
|
||||
byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
|
||||
}
|
||||
|
||||
212
src-csharp/MemScanEDR/Services/PEHeaderAnalyzer.cs
Normal file
212
src-csharp/MemScanEDR/Services/PEHeaderAnalyzer.cs
Normal file
@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
/// <summary>
|
||||
/// PE 头分析器 —— 验证内存中的 PE 镜像与磁盘文件是否一致
|
||||
/// 用于检测 Process Hollowing(进程掏空)攻击
|
||||
/// </summary>
|
||||
public class PEHeaderAnalyzer
|
||||
{
|
||||
private const uint PROCESS_VM_READ = 0x0010;
|
||||
private const uint PROCESS_QUERY_INFORMATION = 0x0400;
|
||||
|
||||
// PE 签名 "MZ"
|
||||
private const ushort MZ_SIGNATURE = 0x5A4D;
|
||||
// PE\0\0 签名
|
||||
private const uint PE_SIGNATURE = 0x00004550;
|
||||
|
||||
/// <summary>
|
||||
/// 检测进程内存中是否存在 PE 头 (MZ 签名)
|
||||
/// </summary>
|
||||
public bool HasInMemoryPE(IntPtr hProcess, ulong baseAddress)
|
||||
{
|
||||
var buffer = new byte[2];
|
||||
if (ReadProcessMemory(hProcess, (IntPtr)baseAddress, buffer, 2, out _))
|
||||
{
|
||||
var mz = BitConverter.ToUInt16(buffer, 0);
|
||||
return mz == MZ_SIGNATURE;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测内存 PE 与磁盘文件是否一致 (Process Hollowing 检测)
|
||||
/// 比较内存中 PE 头的关键字段与磁盘文件
|
||||
/// </summary>
|
||||
public PeIntegrityResult CheckPeIntegrity(string diskPath, IntPtr hProcess, ulong imageBase)
|
||||
{
|
||||
var result = new PeIntegrityResult();
|
||||
|
||||
if (string.IsNullOrEmpty(diskPath) || !File.Exists(diskPath))
|
||||
{
|
||||
result.IsValid = false;
|
||||
result.Reason = "磁盘文件不存在";
|
||||
return result;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 读取磁盘 PE 头
|
||||
var diskPe = File.ReadAllBytes(diskPath);
|
||||
|
||||
// 读取内存 PE 头 (前 4096 字节)
|
||||
var memBuffer = new byte[Math.Min(4096, diskPe.Length)];
|
||||
if (!ReadProcessMemory(hProcess, (IntPtr)imageBase, memBuffer, memBuffer.Length, out _))
|
||||
{
|
||||
result.IsValid = false;
|
||||
result.Reason = "无法读取进程内存";
|
||||
return result;
|
||||
}
|
||||
|
||||
// 比较 DOS 头
|
||||
for (int i = 0; i < Math.Min(64, Math.Min(diskPe.Length, memBuffer.Length)); i++)
|
||||
{
|
||||
if (diskPe[i] != memBuffer[i])
|
||||
{
|
||||
result.MismatchCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 比较 PE 签名位置
|
||||
if (diskPe.Length > 0x3C + 4 && memBuffer.Length > 0x3C + 4)
|
||||
{
|
||||
var diskPeOffset = BitConverter.ToInt32(diskPe, 0x3C);
|
||||
var memPeOffset = BitConverter.ToInt32(memBuffer, 0x3C);
|
||||
|
||||
if (diskPeOffset != memPeOffset)
|
||||
{
|
||||
result.MismatchCount++;
|
||||
}
|
||||
else if (diskPeOffset > 0 && diskPeOffset + 4 <= Math.Min(diskPe.Length, memBuffer.Length))
|
||||
{
|
||||
var diskSig = BitConverter.ToUInt32(diskPe, diskPeOffset);
|
||||
var memSig = BitConverter.ToUInt32(memBuffer, diskPeOffset);
|
||||
if (diskSig != memSig && memSig != PE_SIGNATURE)
|
||||
{
|
||||
result.MismatchCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.IsValid = result.MismatchCount <= 5; // 容忍少量差异
|
||||
result.Reason = result.IsValid
|
||||
? "内存PE与磁盘一致"
|
||||
: $"内存PE与磁盘不一致 ({result.MismatchCount} 处差异) —— 可能存在 Process Hollowing";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.IsValid = false;
|
||||
result.Reason = $"PE 完整性检查异常: {ex.Message}";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从内存中提取可疑字符串 (C2域名、挖矿池、凭证路径等)
|
||||
/// </summary>
|
||||
public List<string> ExtractSuspiciousStrings(IntPtr hProcess, ulong baseAddress, ulong size)
|
||||
{
|
||||
var result = new List<string>();
|
||||
var buffer = new byte[Math.Min((int)size, 65536)];
|
||||
|
||||
if (!ReadProcessMemory(hProcess, (IntPtr)baseAddress, buffer, buffer.Length, out _))
|
||||
return result;
|
||||
|
||||
// 提取ASCII字符串(最小4字符)
|
||||
var current = new StringBuilder();
|
||||
for (int i = 0; i < buffer.Length; i++)
|
||||
{
|
||||
if (buffer[i] >= 0x20 && buffer[i] <= 0x7E)
|
||||
{
|
||||
current.Append((char)buffer[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (current.Length >= 4)
|
||||
{
|
||||
var s = current.ToString();
|
||||
if (IsSuspiciousString(s))
|
||||
result.Add(s);
|
||||
}
|
||||
current.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检测内存中的可疑 API 字符串
|
||||
/// </summary>
|
||||
public List<string> ExtractSuspiciousApis(IntPtr hProcess, ulong baseAddress, ulong size)
|
||||
{
|
||||
var result = new List<string>();
|
||||
var buffer = new byte[Math.Min((int)size, 65536)];
|
||||
|
||||
if (!ReadProcessMemory(hProcess, (IntPtr)baseAddress, buffer, buffer.Length, out _))
|
||||
return result;
|
||||
|
||||
var text = Encoding.ASCII.GetString(buffer);
|
||||
foreach (var api in DangerousApis)
|
||||
{
|
||||
if (text.Contains(api, StringComparison.OrdinalIgnoreCase))
|
||||
result.Add(api);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsSuspiciousString(string s) => s.Length switch
|
||||
{
|
||||
>= 4 when SuspiciousPatterns.Any(p => s.Contains(p, StringComparison.OrdinalIgnoreCase)) => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
private static readonly string[] SuspiciousPatterns = new[]
|
||||
{
|
||||
"stratum+tcp://", "xmr.", "cryptonight", "coinmine", "minergate",
|
||||
".onion", "c2.", "beacon", "meterpreter", "shellcode",
|
||||
"mimikatz", "lsass", "SAM\\", "SYSTEM\\",
|
||||
"YOUR_FILES_ARE_ENCRYPTED", "decrypt", "ransom", "bitcoin",
|
||||
"powershell -enc", "IEX (New-Object", "Invoke-Expression",
|
||||
"keylogger", "keylog", "GetAsyncKeyState",
|
||||
"cmd.exe /c", "schtasks /create", "reg add",
|
||||
"\\AppData\\Roaming\\", "\\Temp\\", "Startup\\"
|
||||
};
|
||||
|
||||
private static readonly string[] DangerousApis = new[]
|
||||
{
|
||||
"VirtualAlloc", "VirtualAllocEx", "VirtualProtect", "VirtualProtectEx",
|
||||
"WriteProcessMemory", "CreateRemoteThread", "NtCreateThreadEx",
|
||||
"RtlCreateUserThread", "QueueUserAPC", "SetThreadContext",
|
||||
"OpenProcess", "NtOpenProcess", "AdjustTokenPrivileges",
|
||||
"CryptEncrypt", "CryptDecrypt", "CryptAcquireContext",
|
||||
"WSASocket", "connect", "send", "recv", "InternetConnect",
|
||||
"URLDownloadToFile", "WinHttpOpen", "WinHttpConnect",
|
||||
"CreateToolhelp32Snapshot", "Process32First", "Module32First",
|
||||
"GetProcAddress", "LoadLibrary", "LdrLoadDll",
|
||||
"NtQuerySystemInformation", "NtQueryInformationProcess",
|
||||
"NtReadVirtualMemory", "NtWriteVirtualMemory",
|
||||
"MiniDumpWriteDump", "SamOpenUser", "SamQueryInformationUser"
|
||||
};
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
|
||||
byte[] lpBuffer, int dwSize, out IntPtr lpNumberOfBytesRead);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// PE 完整性检查结果
|
||||
/// </summary>
|
||||
public class PeIntegrityResult
|
||||
{
|
||||
public bool IsValid { get; set; }
|
||||
public string Reason { get; set; } = "";
|
||||
public int MismatchCount { get; set; }
|
||||
}
|
||||
@ -15,13 +15,6 @@ public class ProcessCollector
|
||||
@"C:\Windows\System32", @"C:\Windows\SysWOW64", @"C:\Windows"
|
||||
};
|
||||
|
||||
private static readonly HashSet<string> SystemNames = new(StringComparer.OrdinalIgnoreCase) {
|
||||
"svchost.exe", "csrss.exe", "winlogon.exe", "services.exe",
|
||||
"lsass.exe", "spoolsv.exe", "smss.exe", "wininit.exe",
|
||||
"dwm.exe", "taskhostw.exe", "sihost.exe", "runtimebroker.exe",
|
||||
"explorer.exe", "system", "registry"
|
||||
};
|
||||
|
||||
public List<ProcessInfo> CollectAll()
|
||||
{
|
||||
var processes = new List<ProcessInfo>();
|
||||
@ -40,6 +33,7 @@ public class ProcessCollector
|
||||
};
|
||||
|
||||
try { info.ExePath = proc.MainModule?.FileName ?? ""; } catch { }
|
||||
try { info.StartTime = proc.StartTime; } catch { }
|
||||
try { info.Ppid = GetParentPid(proc.Id); } catch { }
|
||||
|
||||
if (info.Ppid > 0)
|
||||
@ -50,6 +44,7 @@ public class ProcessCollector
|
||||
|
||||
try { info.CmdLine = GetCommandLine(proc.Id); } catch { }
|
||||
|
||||
// 路径判断
|
||||
var exeLower = info.ExePath.ToLowerInvariant();
|
||||
info.IsSystemPath = SystemPaths.Any(p => exeLower.StartsWith(p.ToLowerInvariant()));
|
||||
info.IsTempPath = exeLower.Contains("\\temp\\") || exeLower.Contains("\\tmp\\") ||
|
||||
@ -57,6 +52,17 @@ public class ProcessCollector
|
||||
info.IsSigned = info.IsSystemPath;
|
||||
info.Signer = info.IsSystemPath ? "Microsoft Windows" : "";
|
||||
|
||||
// v2.0: 获取已加载模块列表
|
||||
try
|
||||
{
|
||||
info.LoadedModules = proc.Modules.Cast<ProcessModule>()
|
||||
.Select(m => m.FileName)
|
||||
.Where(f => !string.IsNullOrEmpty(f))
|
||||
.ToList();
|
||||
}
|
||||
catch { info.LoadedModules = new List<string>(); }
|
||||
|
||||
// 网络连接
|
||||
if (connections.TryGetValue(proc.Id, out var conns))
|
||||
info.Connections = conns;
|
||||
|
||||
@ -118,6 +124,7 @@ public class ProcessCollector
|
||||
Protocol = "tcp",
|
||||
LocalAddr = conn.LocalEndPoint.ToString(),
|
||||
RemoteAddr = conn.RemoteEndPoint.ToString(),
|
||||
RemotePort = conn.RemoteEndPoint.Port,
|
||||
State = conn.State.ToString()
|
||||
});
|
||||
}
|
||||
@ -127,7 +134,7 @@ public class ProcessCollector
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int GetConnectionPid(TcpConnectionInformation conn) => 0; // simplified
|
||||
private static int GetConnectionPid(TcpConnectionInformation conn) => 0; // 简化
|
||||
|
||||
private const uint TH32CS_SNAPPROCESS = 0x00000002;
|
||||
|
||||
|
||||
403
src-csharp/MemScanEDR/Services/RuleEngine.cs
Normal file
403
src-csharp/MemScanEDR/Services/RuleEngine.cs
Normal file
@ -0,0 +1,403 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MemScanEDR.Models;
|
||||
|
||||
namespace MemScanEDR.Services;
|
||||
|
||||
/// <summary>
|
||||
/// MITRE ATT&CK 规则引擎 —— 第三层复核
|
||||
/// 基于 MITRE ATT&CK 框架中的内存攻击技术,对 AI 判定结果进行交叉验证
|
||||
/// 降低误报:AI 标记但规则不匹配 → 降级
|
||||
/// 提高准确率:AI + 规则双重确认 → 确认为恶意
|
||||
/// </summary>
|
||||
public class RuleEngine
|
||||
{
|
||||
private readonly List<MitreRule> _rules;
|
||||
|
||||
public RuleEngine()
|
||||
{
|
||||
_rules = BuildMitreRules();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对 AI 分类结果进行规则复核
|
||||
/// </summary>
|
||||
public RuleValidationResult Validate(MemoryFeatures features, ClassificationResult aiResult,
|
||||
BaselineResult baselineResult)
|
||||
{
|
||||
var result = new RuleValidationResult();
|
||||
var matchedRules = new List<MitreRule>();
|
||||
var totalScore = 0.0;
|
||||
|
||||
foreach (var rule in _rules)
|
||||
{
|
||||
var (matched, confidence) = rule.Evaluate(features);
|
||||
if (matched)
|
||||
{
|
||||
matchedRules.Add(rule);
|
||||
totalScore += confidence;
|
||||
result.MatchedTechniques.Add(rule.TechniqueId);
|
||||
}
|
||||
}
|
||||
|
||||
result.MatchedRuleCount = matchedRules.Count;
|
||||
result.RuleConfidenceScore = Math.Min(totalScore / Math.Max(_rules.Count, 1), 1.0);
|
||||
|
||||
// ─── 三级交叉验证 ───
|
||||
// AI 分数 + 基线异常 + 规则匹配 = 综合判定
|
||||
result.AiScore = aiResult.FinalScore;
|
||||
result.BaselineAnomalyScore = baselineResult.AnomalyScore;
|
||||
result.RuleScore = result.RuleConfidenceScore;
|
||||
|
||||
// 综合分:AI 40% + 基线 30% + 规则 30%
|
||||
var combinedScore = aiResult.FinalScore * 0.40 +
|
||||
baselineResult.AnomalyScore * 0.30 +
|
||||
result.RuleConfidenceScore * 0.30;
|
||||
|
||||
result.CombinedScore = Math.Min(combinedScore, 1.0);
|
||||
|
||||
// 最终风险等级
|
||||
result.FinalRiskLevel = result.CombinedScore switch
|
||||
{
|
||||
>= 0.75 => RiskLevel.High,
|
||||
>= 0.45 => RiskLevel.Suspicious,
|
||||
_ => RiskLevel.Safe
|
||||
};
|
||||
|
||||
// ─── 误报修正逻辑 ───
|
||||
ApplyFalsePositiveCorrections(result, features);
|
||||
|
||||
// 生成综合解释
|
||||
result.Reasoning = BuildReasoning(result, features, matchedRules);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 误报修正 —— 对AI高分但规则不匹配的情况进行降级
|
||||
/// </summary>
|
||||
private static void ApplyFalsePositiveCorrections(RuleValidationResult result, MemoryFeatures features)
|
||||
{
|
||||
// 修正1: JIT 编译器合法 RWX (.NET, Java, V8)
|
||||
if (features.RwxPrivatePages > 0 && result.MatchedRuleCount == 0)
|
||||
{
|
||||
var name = features.ProcessName.ToLowerInvariant();
|
||||
var isRuntime = name.Contains("dotnet") || name.Contains("java") ||
|
||||
name.Contains("node") || name.Contains("python") ||
|
||||
name.Contains("chrome") || name.Contains("msedge") ||
|
||||
name.Contains("firefox");
|
||||
|
||||
if (isRuntime && features.IsSystemPath && features.IsSigned)
|
||||
{
|
||||
// JIT 运行时正常使用 RWX,降级
|
||||
result.FinalRiskLevel = RiskLevel.Safe;
|
||||
result.CombinedScore = Math.Min(result.CombinedScore, 0.25);
|
||||
result.FalsePositiveCorrection = "JIT运行时合法RWX内存(.NET/Java/V8引擎)";
|
||||
}
|
||||
}
|
||||
|
||||
// 修正2: 安全软件正常行为
|
||||
if (features.SuspiciousApiStrings.Count > 0 && result.MatchedRuleCount <= 1)
|
||||
{
|
||||
var nameLower = features.ProcessName.ToLowerInvariant();
|
||||
var isSecurity = nameLower.Contains("defender") || nameLower.Contains("antivirus") ||
|
||||
nameLower.Contains("security") || nameLower.Contains("edr") ||
|
||||
nameLower.Contains("sentinel") || nameLower.Contains("crowd") ||
|
||||
nameLower.Contains("carbon") || nameLower.Contains("mcafee");
|
||||
|
||||
if (isSecurity && features.IsSigned)
|
||||
{
|
||||
result.FinalRiskLevel = RiskLevel.Safe;
|
||||
result.CombinedScore = Math.Min(result.CombinedScore, 0.20);
|
||||
result.FalsePositiveCorrection = "安全软件正常行为特征";
|
||||
return; // 已经确定是误报,无需继续检查
|
||||
}
|
||||
}
|
||||
|
||||
// 修正2.5: 脚本引擎/Shell自身字符串误匹配
|
||||
if (features.SuspiciousStringHits > 0 && result.MatchedRuleCount <= 1)
|
||||
{
|
||||
var nameLower = features.ProcessName.ToLowerInvariant();
|
||||
var isScriptEngine = nameLower is "powershell.exe" or "pwsh.exe" or "powershell_ise.exe"
|
||||
or "cmd.exe" or "wscript.exe" or "cscript.exe" or "conhost.exe"
|
||||
or "python.exe" or "python3.exe" or "wsl.exe" or "bash.exe";
|
||||
|
||||
if (isScriptEngine && features.IsSigned && features.IsSystemPath)
|
||||
{
|
||||
result.FinalRiskLevel = RiskLevel.Safe;
|
||||
result.CombinedScore = Math.Min(result.CombinedScore, 0.15);
|
||||
result.FalsePositiveCorrection = "脚本引擎自身合法字符串(非恶意注入)";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 修正3: 开发工具调试行为
|
||||
if (features.SuspiciousApiStrings.Count > 0 && result.MatchedRuleCount <= 1)
|
||||
{
|
||||
var exeLower = features.ExePath.ToLowerInvariant();
|
||||
var isDev = exeLower.Contains("visual studio") || exeLower.Contains("jetbrains") ||
|
||||
exeLower.Contains("vscode") || exeLower.Contains("debugger") ||
|
||||
exeLower.Contains("ollydbg") || exeLower.Contains("x64dbg") ||
|
||||
exeLower.Contains("windbg") || exeLower.Contains("ida");
|
||||
|
||||
if (isDev)
|
||||
{
|
||||
result.FinalRiskLevel = RiskLevel.Safe;
|
||||
result.CombinedScore = Math.Min(result.CombinedScore, 0.30);
|
||||
result.FalsePositiveCorrection = "开发/调试工具正常行为";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildReasoning(RuleValidationResult result, MemoryFeatures f,
|
||||
List<MitreRule> matchedRules)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (result.FalsePositiveCorrection != null)
|
||||
{
|
||||
parts.Add($"误报修正: {result.FalsePositiveCorrection}");
|
||||
}
|
||||
|
||||
if (matchedRules.Count > 0)
|
||||
{
|
||||
var ruleDescriptions = matchedRules.Take(3)
|
||||
.Select(r => $"{r.TechniqueId} ({r.TechniqueName})");
|
||||
parts.Add($"匹配MITRE规则: {string.Join(", ", ruleDescriptions)}");
|
||||
}
|
||||
|
||||
if (f.RwxPrivatePages > 0)
|
||||
parts.Add($"RWX私有页: {f.RwxPrivatePages}");
|
||||
|
||||
if (f.PeMismatchDisk)
|
||||
parts.Add("Process Hollowing 特征");
|
||||
|
||||
if (f.SuspiciousStringHits > 0)
|
||||
parts.Add($"敏感字符串命中: {f.SuspiciousStringHits}");
|
||||
|
||||
parts.Add($"综合评分: AI={result.AiScore:F2} 基线={result.BaselineAnomalyScore:F2} 规则={result.RuleScore:F2} → {result.CombinedScore:F2}");
|
||||
|
||||
return string.Join(" | ", parts);
|
||||
}
|
||||
|
||||
// ─── MITRE ATT&CK 内存攻击规则库 ───
|
||||
private static List<MitreRule> BuildMitreRules() => new()
|
||||
{
|
||||
// T1055: Process Injection
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1055",
|
||||
TechniqueName = "进程注入",
|
||||
Description = "通过 VirtualAllocEx/WriteProcessMemory/CreateRemoteThread 实现跨进程代码注入",
|
||||
EvaluateFunc = f => f.RwxPrivatePages > 0 &&
|
||||
f.SuspiciousApiStrings.Any(a =>
|
||||
a.Contains("VirtualAllocEx", StringComparison.OrdinalIgnoreCase) ||
|
||||
a.Contains("WriteProcessMemory", StringComparison.OrdinalIgnoreCase) ||
|
||||
a.Contains("CreateRemoteThread", StringComparison.OrdinalIgnoreCase))
|
||||
},
|
||||
|
||||
// T1055.012: Process Hollowing
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1055.012",
|
||||
TechniqueName = "进程掏空 (Process Hollowing)",
|
||||
Description = "创建挂起进程后掏空原始代码,替换为恶意载荷",
|
||||
EvaluateFunc = f => f.PeMismatchDisk
|
||||
},
|
||||
|
||||
// T1055.001: Dynamic-link Library Injection
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1055.001",
|
||||
TechniqueName = "DLL注入",
|
||||
Description = "通过 LoadLibrary/CreateRemoteThread 注入恶意DLL",
|
||||
EvaluateFunc = f => f.SuspiciousApiStrings.Any(a =>
|
||||
a.Contains("LoadLibrary", StringComparison.OrdinalIgnoreCase) ||
|
||||
a.Contains("LdrLoadDll", StringComparison.OrdinalIgnoreCase)) &&
|
||||
f.SuspiciousApiStrings.Any(a =>
|
||||
a.Contains("CreateRemoteThread", StringComparison.OrdinalIgnoreCase))
|
||||
},
|
||||
|
||||
// T1620: Reflective Code Loading
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1620",
|
||||
TechniqueName = "反射代码加载",
|
||||
Description = "在内存中直接加载PE/DLL,不通过标准 LoadLibrary",
|
||||
EvaluateFunc = f => f.AnonymousExecPages > 0 &&
|
||||
f.RwxPrivatePages > 0 &&
|
||||
f.SuspiciousStrings.Any(s =>
|
||||
s.Contains("ReflectiveLoader", StringComparison.OrdinalIgnoreCase))
|
||||
},
|
||||
|
||||
// T1574.002: DLL Side-Loading
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1574.002",
|
||||
TechniqueName = "DLL侧加载",
|
||||
Description = "利用合法程序的DLL搜索顺序加载恶意DLL",
|
||||
EvaluateFunc = f => f.TempPathModules > 0 && f.IsSigned && f.IsSystemPath
|
||||
},
|
||||
|
||||
// T1071: Application Layer Protocol (C2)
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1071",
|
||||
TechniqueName = "C2 通信",
|
||||
Description = "通过应用层协议与C2服务器通信",
|
||||
EvaluateFunc = f => f.ForeignConnections > 0 &&
|
||||
f.SuspiciousPortConnections > 0 &&
|
||||
f.SuspiciousApiStrings.Any(a =>
|
||||
a.Contains("connect", StringComparison.OrdinalIgnoreCase) ||
|
||||
a.Contains("InternetConnect", StringComparison.OrdinalIgnoreCase))
|
||||
},
|
||||
|
||||
// T1095: Non-Application Layer Protocol
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1095",
|
||||
TechniqueName = "非标准协议通信",
|
||||
Description = "使用非标准协议的网络通信",
|
||||
EvaluateFunc = f => f.NonStandardPortConnections >= 2 &&
|
||||
f.ForeignConnections > 0
|
||||
},
|
||||
|
||||
// T1027: Obfuscated Files or Information
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1027",
|
||||
TechniqueName = "代码混淆/加密",
|
||||
Description = "内存中存在加密或混淆载荷",
|
||||
EvaluateFunc = f => f.MaxEntropy > 7.2 &&
|
||||
f.HighEntropyRegions >= 2 &&
|
||||
f.AnonymousExecPages > 0
|
||||
},
|
||||
|
||||
// T1003.001: LSASS Memory (Credential Dumping)
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1003.001",
|
||||
TechniqueName = "LSASS 凭据转储",
|
||||
Description = "访问 LSASS 进程内存提取凭据",
|
||||
EvaluateFunc = f => f.ProcessName.Equals("lsass.exe", StringComparison.OrdinalIgnoreCase) &&
|
||||
f.SuspiciousApiStrings.Any(a =>
|
||||
a.Contains("MiniDump", StringComparison.OrdinalIgnoreCase) ||
|
||||
a.Contains("SamOpenUser", StringComparison.OrdinalIgnoreCase)) ||
|
||||
f.SuspiciousStrings.Any(s =>
|
||||
s.Contains("lsass", StringComparison.OrdinalIgnoreCase))
|
||||
},
|
||||
|
||||
// T1036.005: Match Legitimate Name or Location
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1036.005",
|
||||
TechniqueName = "进程名伪装",
|
||||
Description = "使用合法的系统进程名但位于非系统路径",
|
||||
EvaluateFunc = f => f.IsSystemProcessName && !f.IsSystemPath
|
||||
},
|
||||
|
||||
// T1547.001: Registry Run Keys
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1547.001",
|
||||
TechniqueName = "注册表持久化",
|
||||
Description = "通过注册表run键实现持久化驻留",
|
||||
EvaluateFunc = f => f.SuspiciousStrings.Any(s =>
|
||||
s.Contains("Run", StringComparison.OrdinalIgnoreCase) &&
|
||||
(s.Contains("SOFTWARE\\Microsoft\\Windows\\CurrentVersion", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Contains("RunOnce", StringComparison.OrdinalIgnoreCase)))
|
||||
},
|
||||
|
||||
// T1496: Resource Hijacking (Crypto Mining)
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1496",
|
||||
TechniqueName = "资源劫持 (挖矿)",
|
||||
Description = "劫持系统资源进行加密货币挖矿",
|
||||
EvaluateFunc = f => f.SuspiciousStrings.Any(s =>
|
||||
s.Contains("stratum", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Contains("cryptonight", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Contains("coinmine", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Contains("mining", StringComparison.OrdinalIgnoreCase))
|
||||
},
|
||||
|
||||
// T1486: Data Encrypted for Impact (Ransomware)
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1486",
|
||||
TechniqueName = "勒索加密",
|
||||
Description = "加密文件以勒索赎金",
|
||||
EvaluateFunc = f => f.SuspiciousStrings.Any(s =>
|
||||
s.Contains("ENCRYPTED", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Contains("ransom", StringComparison.OrdinalIgnoreCase) ||
|
||||
s.Contains("decrypt", StringComparison.OrdinalIgnoreCase)) &&
|
||||
f.MaxEntropy > 7.0
|
||||
},
|
||||
|
||||
// T1562.001: Disable or Modify Tools
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1562.001",
|
||||
TechniqueName = "禁用安全工具",
|
||||
Description = "禁用或修改安全软件",
|
||||
EvaluateFunc = f => f.SuspiciousApiStrings.Any(a =>
|
||||
a.Contains("AdjustTokenPrivileges", StringComparison.OrdinalIgnoreCase)) &&
|
||||
f.IsSystemProcessName
|
||||
},
|
||||
|
||||
// T1106: Native API
|
||||
new MitreRule
|
||||
{
|
||||
TechniqueId = "T1106",
|
||||
TechniqueName = "Native API 调用",
|
||||
Description = "通过NT原生API绕过用户态Hook",
|
||||
EvaluateFunc = f => f.SuspiciousApiStrings.Count(a =>
|
||||
a.StartsWith("Nt", StringComparison.OrdinalIgnoreCase)) >= 2
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MITRE ATT&CK 单条规则定义
|
||||
/// </summary>
|
||||
public class MitreRule
|
||||
{
|
||||
public string TechniqueId { get; init; } = "";
|
||||
public string TechniqueName { get; init; } = "";
|
||||
public string Description { get; init; } = "";
|
||||
public Func<MemoryFeatures, bool> EvaluateFunc { get; init; } = _ => false;
|
||||
|
||||
/// <summary>
|
||||
/// 评估规则,返回 (是否匹配, 置信度)
|
||||
/// </summary>
|
||||
public (bool matched, double confidence) Evaluate(MemoryFeatures features)
|
||||
{
|
||||
try
|
||||
{
|
||||
var match = EvaluateFunc(features);
|
||||
return (match, match ? 1.0 : 0.0);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return (false, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 规则引擎验证结果
|
||||
/// </summary>
|
||||
public class RuleValidationResult
|
||||
{
|
||||
public double AiScore { get; set; }
|
||||
public double BaselineAnomalyScore { get; set; }
|
||||
public double RuleScore { get; set; }
|
||||
public double CombinedScore { get; set; }
|
||||
public RiskLevel FinalRiskLevel { get; set; } = RiskLevel.Safe;
|
||||
public int MatchedRuleCount { get; set; }
|
||||
public List<string> MatchedTechniques { get; set; } = new();
|
||||
public double RuleConfidenceScore { get; set; }
|
||||
public string Reasoning { get; set; } = "";
|
||||
public string? FalsePositiveCorrection { get; set; }
|
||||
}
|
||||
@ -1,10 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<assemblyIdentity version="2.0.0.0" name="MemScanEDR.app"/>
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
|
||||
<security>
|
||||
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
|
||||
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- Windows 10 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> <!-- Windows 11 -->
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user