Compare commits

...

No commits in common. "v1.2.0" and "main" have entirely different histories.
v1.2.0 ... main

27 changed files with 3202 additions and 851 deletions

35
.gitignore vendored
View File

@ -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
View 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
View File

@ -4,16 +4,27 @@
</div>
# MemScan-EDR
# MemScan-EDR v2.0
> **开源、轻量化、内存专项的 Windows 终端威胁检测与响应 (EDR) 工具**
> **AI 驱动的内存威胁检测引擎 — 三级联动检测管线**
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![.NET 8](https://img.shields.io/badge/.NET-8.0-purple.svg)](https://dotnet.microsoft.com/)
[![Platform: Windows](https://img.shields.io/badge/Platform-Windows%2010%2F11-lightgrey.svg)](https://www.microsoft.com/windows)
[![CS-WPF](https://img.shields.io/badge/UI-WPF-green.svg)](https://github.com/dotnet/wpf)
MemScan-EDR 扫描所有运行中的进程,检查内存中的 Shellcode / 注入 / C2 特征,并利用 AIOpenAI / 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 等)无任何关联。
>
> 本工具仅用于**合法的安全研究、教育及授权的安全评估**。未经授权对非自有或未被许可的系统使用属违法行为。使用者须遵守当地法律法规并自行承担所有风险。
---

View File

@ -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: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![.NET 8](https://img.shields.io/badge/.NET-8.0-purple.svg)](https://dotnet.microsoft.com/)
[![Platform: Windows](https://img.shields.io/badge/Platform-Windows%2010%2F11-lightgrey.svg)](https://www.microsoft.com/windows)
[![CS-WPF](https://img.shields.io/badge/UI-WPF-green.svg)](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.
---

View File

@ -1 +0,0 @@


16
scripts/README.md Normal file
View 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.

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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 &amp; 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>

View File

@ -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) { }
}

View File

@ -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
{

View 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;
}
}

View 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;
}

View File

@ -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;
}

View File

@ -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.01.0 (0.00.30=safe, 0.310.69=suspicious, 0.701.0=high)
- indicators: list of 05 key indicator strings (e.g. ""unsigned_temp_binary"", ""rwx_shellcode_region"", ""c2_network"", ""process_masquerading"", ""dll_injection_sig"")
- reasoning: 12 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"":""""}";
}

View 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
}

View File

@ -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;
}
}
}

View 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
}

View 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
}

View 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; }
}

View 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);
}

View File

@ -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);
}

View 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; }
}

View File

@ -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;

View 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; }
}

View File

@ -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>
<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>