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