451 lines
18 KiB
C#
451 lines
18 KiB
C#
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Net.Http;
|
|||
|
|
using System.Text;
|
|||
|
|
using System.Text.Json;
|
|||
|
|
using System.Threading.Tasks;
|
|||
|
|
using MemScanEDR.Models;
|
|||
|
|
|
|||
|
|
namespace MemScanEDR.Services;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// AI 分析器 v2.0 —— 重构版
|
|||
|
|
/// 核心变化:
|
|||
|
|
/// - 默认使用本地 AI 管线(DetectionPipeline:基线+分类+规则三级联动)
|
|||
|
|
/// - LLM 大模型仅用于辅助研判(可选,需手动开启)
|
|||
|
|
/// - 启发式规则引擎作为降级方案保留
|
|||
|
|
/// </summary>
|
|||
|
|
public class AIAnalyzer
|
|||
|
|
{
|
|||
|
|
private readonly HttpClient _http = new();
|
|||
|
|
private AIConfig _config;
|
|||
|
|
private readonly DetectionPipeline _pipeline;
|
|||
|
|
private readonly List<Signature> _signatures;
|
|||
|
|
|
|||
|
|
public AIAnalyzer(AIConfig config, List<Signature> signatures, ScanMode scanMode = ScanMode.Balanced)
|
|||
|
|
{
|
|||
|
|
_config = config;
|
|||
|
|
_signatures = signatures;
|
|||
|
|
_http.Timeout = TimeSpan.FromSeconds(config.Timeout);
|
|||
|
|
_pipeline = new DetectionPipeline(scanMode);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public void UpdateConfig(AIConfig config)
|
|||
|
|
{
|
|||
|
|
_config = config;
|
|||
|
|
_http.Timeout = TimeSpan.FromSeconds(config.Timeout);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 分析进程 —— v2.0 核心入口
|
|||
|
|
/// </summary>
|
|||
|
|
public async Task<ScanResult> AnalyzeProcessAsync(ProcessInfo proc,
|
|||
|
|
List<SignatureMatch>? matches = null, List<MemoryRegion>? suspiciousRegions = null)
|
|||
|
|
{
|
|||
|
|
// v2.0: 优先使用本地 AI 管线
|
|||
|
|
if (_config.EnableLocalAI)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var result = await _pipeline.AnalyzeAsync(proc, _signatures);
|
|||
|
|
|
|||
|
|
// 如果启用了 LLM 辅助研判且本地 AI 判定为可疑/高危
|
|||
|
|
if (_config.EnableLLMAssist &&
|
|||
|
|
result.RiskLevel != RiskLevel.Safe &&
|
|||
|
|
_config.Provider != AIProvider.Heuristic)
|
|||
|
|
{
|
|||
|
|
var llmResult = await LlmAssistedAnalysis(proc, result);
|
|||
|
|
if (llmResult != null)
|
|||
|
|
{
|
|||
|
|
// LLM 结果作为补充参考,不覆盖本地 AI 的核心判定
|
|||
|
|
result.Reasoning += $" | LLM研判: {llmResult.Reasoning}";
|
|||
|
|
result.AIProvider += " + LLM";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
// 管线异常时回退
|
|||
|
|
return HeuristicAnalyze(proc, matches ?? new(), suspiciousRegions ?? new(),
|
|||
|
|
$"管线异常回退: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 未启用本地AI时的传统模式
|
|||
|
|
if (_config.Provider == AIProvider.Heuristic)
|
|||
|
|
{
|
|||
|
|
return HeuristicAnalyze(proc, matches ?? new(), suspiciousRegions ?? new());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 传统 LLM 模式(兼容 v1.0)
|
|||
|
|
return await LegacyApiAnalyze(proc, matches ?? new(), suspiciousRegions ?? new());
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// LLM 辅助研判 —— 仅对本地 AI 已标记为可疑的进程做二次确认
|
|||
|
|
/// </summary>
|
|||
|
|
private async Task<ScanResult?> LlmAssistedAnalysis(ProcessInfo proc, ScanResult pipelineResult)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var prompt = BuildLlmAssistPrompt(proc, pipelineResult);
|
|||
|
|
var response = await CallAIAsync(prompt);
|
|||
|
|
return ParseLlmResponse(proc, response);
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static string BuildLlmAssistPrompt(ProcessInfo proc, ScanResult pipelineResult)
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("=== 本地AI已标记为可疑的进程 ===");
|
|||
|
|
sb.AppendLine($"PID: {proc.Pid}, 进程名: {proc.Name}, 路径: {proc.ExePath}");
|
|||
|
|
sb.AppendLine($"风险等级: {pipelineResult.RiskLevel}, 评分: {pipelineResult.RiskScore:F2}");
|
|||
|
|
sb.AppendLine($"指标: {string.Join(", ", pipelineResult.Indicators)}");
|
|||
|
|
sb.AppendLine($"检测层: {pipelineResult.DetectionLayer}");
|
|||
|
|
sb.AppendLine($"本地AI推理: {pipelineResult.Reasoning}");
|
|||
|
|
sb.AppendLine("\n=== 请结合以上信息做最终研判 ===");
|
|||
|
|
sb.AppendLine("请判断此进程是否为真正的威胁,并给出简短理由。");
|
|||
|
|
sb.AppendLine("注意:.NET/Java/Node.js/Chrome等JIT运行时可能有合法RWX内存,安全软件可能有注入行为。");
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private ScanResult ParseLlmResponse(ProcessInfo proc, string content)
|
|||
|
|
{
|
|||
|
|
return new ScanResult
|
|||
|
|
{
|
|||
|
|
Pid = proc.Pid,
|
|||
|
|
ProcessName = proc.Name,
|
|||
|
|
RiskLevel = RiskLevel.Safe,
|
|||
|
|
RiskScore = 0,
|
|||
|
|
Reasoning = content.Trim().Length > 200 ? content.Trim()[..200] : content.Trim(),
|
|||
|
|
AIProvider = $"llm-assist/{_config.Model}"
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 传统 LLM API 分析(兼容 v1.0 模式)
|
|||
|
|
/// </summary>
|
|||
|
|
private async Task<ScanResult> LegacyApiAnalyze(ProcessInfo proc,
|
|||
|
|
List<SignatureMatch> matches, List<MemoryRegion> suspiciousRegions)
|
|||
|
|
{
|
|||
|
|
var preScore = QuickPreScore(proc, matches, suspiciousRegions);
|
|||
|
|
if (preScore < 0.05)
|
|||
|
|
{
|
|||
|
|
return new ScanResult
|
|||
|
|
{
|
|||
|
|
Pid = proc.Pid,
|
|||
|
|
ProcessName = proc.Name,
|
|||
|
|
RiskLevel = RiskLevel.Safe,
|
|||
|
|
RiskScore = 0.0,
|
|||
|
|
Indicators = new List<string>(),
|
|||
|
|
Reasoning = $"PID {proc.Pid} ({proc.Name}) | v2.0 预过滤跳过",
|
|||
|
|
AIProvider = "heuristic/pre-filter-v2",
|
|||
|
|
DetectionLayer = "PreFilter",
|
|||
|
|
ScanTime = DateTime.Now
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
var prompt = BuildLegacyPrompt(proc, matches, suspiciousRegions);
|
|||
|
|
var response = await CallAIAsync(prompt);
|
|||
|
|
return ParseLegacyResponse(proc, response);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
return HeuristicAnalyze(proc, matches, suspiciousRegions,
|
|||
|
|
$"API异常回退: {ex.Message}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static double QuickPreScore(ProcessInfo proc, List<SignatureMatch> matches,
|
|||
|
|
List<MemoryRegion> suspiciousRegions)
|
|||
|
|
{
|
|||
|
|
double s = 0;
|
|||
|
|
if (!proc.IsSigned && proc.IsTempPath) s += 0.30;
|
|||
|
|
if (FeatureExtractor.IsSystemProcessName(proc.Name) && !proc.IsSystemPath) s += 0.25;
|
|||
|
|
var suspPorts = new HashSet<int> { 4444, 5555, 6666, 7777, 1337, 31337, 9999, 12345, 27015 };
|
|||
|
|
if (proc.Connections.Any(c => suspPorts.Contains(c.RemotePort))) s += 0.20;
|
|||
|
|
if (matches.Count > 0) s += 0.20;
|
|||
|
|
if (suspiciousRegions.Any(r => r.Protect.Contains("EXECUTE_READWRITE"))) s += 0.15;
|
|||
|
|
return Math.Min(s, 1.0);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── 启发式规则(保留作为降级方案)───
|
|||
|
|
public ScanResult HeuristicAnalyze(ProcessInfo proc, List<SignatureMatch> matches,
|
|||
|
|
List<MemoryRegion> suspiciousRegions, string? errorNote = null)
|
|||
|
|
{
|
|||
|
|
double score = 0;
|
|||
|
|
var indicators = new List<string>();
|
|||
|
|
var reasons = new List<string>();
|
|||
|
|
|
|||
|
|
// Rule 1: unsigned + temp path
|
|||
|
|
if (!proc.IsSigned && proc.IsTempPath)
|
|||
|
|
{
|
|||
|
|
score += 0.30;
|
|||
|
|
indicators.Add("unsigned_temp_path");
|
|||
|
|
reasons.Add("unsigned executable in temp directory");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Rule 2: process masquerading
|
|||
|
|
if (FeatureExtractor.IsSystemProcessName(proc.Name) && !proc.IsSystemPath)
|
|||
|
|
{
|
|||
|
|
score += 0.25;
|
|||
|
|
indicators.Add("process_masquerading");
|
|||
|
|
reasons.Add("system process name in non-system path");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Rule 3: suspicious ports
|
|||
|
|
var suspPorts = new HashSet<int> { 4444, 5555, 6666, 7777, 1337, 31337 };
|
|||
|
|
foreach (var conn in proc.Connections)
|
|||
|
|
{
|
|||
|
|
if (suspPorts.Contains(conn.RemotePort))
|
|||
|
|
{
|
|||
|
|
score += 0.20;
|
|||
|
|
indicators.Add("suspicious_port");
|
|||
|
|
reasons.Add($"suspicious remote port {conn.RemotePort}");
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Rule 4: signature matches
|
|||
|
|
if (matches.Count > 0)
|
|||
|
|
{
|
|||
|
|
var high = matches.Count(m => m.Severity == "high");
|
|||
|
|
var med = matches.Count(m => m.Severity == "medium");
|
|||
|
|
score += Math.Min(high * 0.20, 0.4);
|
|||
|
|
score += Math.Min(med * 0.08, 0.2);
|
|||
|
|
if (high > 0) { indicators.Add("high_severity_match"); reasons.Add($"{high} high-severity signatures"); }
|
|||
|
|
if (matches.Any(m => m.Category == "shellcode")) { score += 0.15; indicators.Add("shellcode_detected"); }
|
|||
|
|
if (matches.Any(m => m.Category == "c2")) { score += 0.20; indicators.Add("c2_pattern"); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Rule 5: suspicious memory regions (v2.0 improved: distinguish JIT)
|
|||
|
|
var rwxCount = suspiciousRegions.Count(r =>
|
|||
|
|
r.Protect.Contains("EXECUTE_READWRITE") && r.Type == "PRIVATE");
|
|||
|
|
|
|||
|
|
// v2.0 improvement: don't flag JIT runtimes
|
|||
|
|
var isRuntime = proc.Name.ToLowerInvariant() switch
|
|||
|
|
{
|
|||
|
|
var n when n.Contains("dotnet") || n.Contains("java") ||
|
|||
|
|
n.Contains("node") || n.Contains("python") ||
|
|||
|
|
n.Contains("chrome") || n.Contains("msedge") ||
|
|||
|
|
n.Contains("firefox") => true,
|
|||
|
|
_ => false
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (rwxCount > 0 && !isRuntime)
|
|||
|
|
{
|
|||
|
|
score += Math.Min(rwxCount * 0.12, 0.25);
|
|||
|
|
indicators.Add("rwx_private_memory");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
score = Math.Min(score, 1.0);
|
|||
|
|
var level = score >= 0.75 ? RiskLevel.High : score >= 0.50 ? RiskLevel.Suspicious : RiskLevel.Safe;
|
|||
|
|
|
|||
|
|
var reasoning = $"PID {proc.Pid} ({proc.Name}) | {string.Join("; ", reasons)} | score={score:F2}";
|
|||
|
|
if (errorNote != null)
|
|||
|
|
reasoning += $" | {errorNote}";
|
|||
|
|
|
|||
|
|
return new ScanResult
|
|||
|
|
{
|
|||
|
|
Pid = proc.Pid,
|
|||
|
|
ProcessName = proc.Name,
|
|||
|
|
RiskLevel = level,
|
|||
|
|
RiskScore = score,
|
|||
|
|
Indicators = indicators,
|
|||
|
|
Reasoning = reasoning,
|
|||
|
|
AIProvider = "heuristic/rule-engine-v2",
|
|||
|
|
DetectionLayer = "Heuristic",
|
|||
|
|
ScanTime = DateTime.Now
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ─── LLM API 调用 ───
|
|||
|
|
private async Task<string> CallAIAsync(string userMessage)
|
|||
|
|
{
|
|||
|
|
if (_config.Provider == AIProvider.Claude)
|
|||
|
|
return await CallClaudeAsync(userMessage);
|
|||
|
|
|
|||
|
|
var url = _config.Provider == AIProvider.Local
|
|||
|
|
? $"{_config.BaseUrl}/api/chat"
|
|||
|
|
: $"{_config.BaseUrl.TrimEnd('/')}/chat/completions";
|
|||
|
|
|
|||
|
|
object requestBody = _config.Provider == AIProvider.Local
|
|||
|
|
? new { model = _config.Model, messages = new[] { new { role = "system", content = V2SystemPrompt }, new { role = "user", content = userMessage } }, stream = false }
|
|||
|
|
: new { model = _config.Model, messages = new[] { new { role = "system", content = V2SystemPrompt }, new { role = "user", content = userMessage } }, max_tokens = _config.MaxTokens, temperature = _config.Temperature };
|
|||
|
|
|
|||
|
|
var json = JsonSerializer.Serialize(requestBody);
|
|||
|
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
|||
|
|
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(_config.ApiKey))
|
|||
|
|
request.Headers.Add("Authorization", $"Bearer {_config.ApiKey}");
|
|||
|
|
|
|||
|
|
var response = await _http.SendAsync(request);
|
|||
|
|
response.EnsureSuccessStatusCode();
|
|||
|
|
var responseJson = await response.Content.ReadAsStringAsync();
|
|||
|
|
|
|||
|
|
using var doc = JsonDocument.Parse(responseJson);
|
|||
|
|
if (_config.Provider == AIProvider.Local)
|
|||
|
|
return doc.RootElement.GetProperty("message").GetProperty("content").GetString() ?? "";
|
|||
|
|
return doc.RootElement.GetProperty("choices")[0].GetProperty("message").GetProperty("content").GetString() ?? "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task<string> CallClaudeAsync(string userMessage)
|
|||
|
|
{
|
|||
|
|
var url = $"{_config.BaseUrl.TrimEnd('/')}/v1/messages";
|
|||
|
|
var requestBody = new
|
|||
|
|
{
|
|||
|
|
model = _config.Model,
|
|||
|
|
max_tokens = _config.MaxTokens,
|
|||
|
|
system = V2SystemPrompt,
|
|||
|
|
messages = new[] { new { role = "user", content = userMessage } }
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
var json = JsonSerializer.Serialize(requestBody);
|
|||
|
|
var request = new HttpRequestMessage(HttpMethod.Post, url);
|
|||
|
|
request.Content = new StringContent(json, Encoding.UTF8, "application/json");
|
|||
|
|
request.Headers.Add("x-api-key", _config.ApiKey);
|
|||
|
|
request.Headers.Add("anthropic-version", "2023-06-01");
|
|||
|
|
|
|||
|
|
var response = await _http.SendAsync(request);
|
|||
|
|
response.EnsureSuccessStatusCode();
|
|||
|
|
var responseJson = await response.Content.ReadAsStringAsync();
|
|||
|
|
|
|||
|
|
using var doc = JsonDocument.Parse(responseJson);
|
|||
|
|
return doc.RootElement.GetProperty("content")[0].GetProperty("text").GetString() ?? "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private string BuildLegacyPrompt(ProcessInfo proc, List<SignatureMatch> matches,
|
|||
|
|
List<MemoryRegion> suspiciousRegions)
|
|||
|
|
{
|
|||
|
|
var sb = new StringBuilder();
|
|||
|
|
sb.AppendLine("=== PROCESS DATA ===");
|
|||
|
|
sb.AppendLine($"PID: {proc.Pid}");
|
|||
|
|
sb.AppendLine($"Name: {proc.Name}");
|
|||
|
|
sb.AppendLine($"Path: {(string.IsNullOrEmpty(proc.ExePath) ? "unknown" : proc.ExePath)}");
|
|||
|
|
sb.AppendLine($"Parent: {proc.ParentName} (PPID: {proc.Ppid})");
|
|||
|
|
sb.AppendLine($"CommandLine: {proc.CmdLine}");
|
|||
|
|
sb.AppendLine($"Signed: {proc.IsSigned}");
|
|||
|
|
sb.AppendLine($"Signer: {(string.IsNullOrEmpty(proc.Signer) ? "-" : proc.Signer)}");
|
|||
|
|
sb.AppendLine($"SystemPath: {proc.IsSystemPath}");
|
|||
|
|
sb.AppendLine($"TempPath: {proc.IsTempPath}");
|
|||
|
|
|
|||
|
|
if (proc.Connections.Count > 0)
|
|||
|
|
{
|
|||
|
|
sb.AppendLine($"\nNetworkConnections ({proc.Connections.Count}):");
|
|||
|
|
foreach (var c in proc.Connections.Take(8))
|
|||
|
|
sb.AppendLine($" {c.Protocol} {c.LocalAddr} -> {c.RemoteAddr} [{c.State}]");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (matches.Count > 0)
|
|||
|
|
{
|
|||
|
|
sb.AppendLine($"\nMemorySignatures ({matches.Count}):");
|
|||
|
|
foreach (var m in matches.Take(10))
|
|||
|
|
sb.AppendLine($" [{m.Severity}] {m.SigId} @ {m.Address}: {m.Description}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (suspiciousRegions.Count > 0)
|
|||
|
|
{
|
|||
|
|
sb.AppendLine($"\nSuspiciousMemoryRegions ({suspiciousRegions.Count}):");
|
|||
|
|
foreach (var r in suspiciousRegions.Take(5))
|
|||
|
|
sb.AppendLine($" 0x{r.BaseAddress:X} size={r.Size} protect={r.Protect} type={r.Type}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
sb.AppendLine("\n=== ANALYZE THIS PROCESS ===");
|
|||
|
|
return sb.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private static ScanResult ParseLegacyResponse(ProcessInfo proc, string content)
|
|||
|
|
{
|
|||
|
|
content = content.Trim();
|
|||
|
|
if (content.StartsWith("```"))
|
|||
|
|
content = string.Join('\n', content.Split('\n')[1..^1]);
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
using var doc = JsonDocument.Parse(content);
|
|||
|
|
var root = doc.RootElement;
|
|||
|
|
var riskLevel = root.GetProperty("risk_level").GetString() switch
|
|||
|
|
{
|
|||
|
|
"high" => RiskLevel.High,
|
|||
|
|
"suspicious" => RiskLevel.Suspicious,
|
|||
|
|
_ => RiskLevel.Safe
|
|||
|
|
};
|
|||
|
|
var indicators = new List<string>();
|
|||
|
|
if (root.TryGetProperty("indicators", out var indArr))
|
|||
|
|
foreach (var ind in indArr.EnumerateArray())
|
|||
|
|
indicators.Add(ind.GetString() ?? "");
|
|||
|
|
|
|||
|
|
return new ScanResult
|
|||
|
|
{
|
|||
|
|
Pid = proc.Pid,
|
|||
|
|
ProcessName = proc.Name,
|
|||
|
|
RiskLevel = riskLevel,
|
|||
|
|
RiskScore = root.GetProperty("risk_score").GetDouble(),
|
|||
|
|
Indicators = indicators,
|
|||
|
|
Reasoning = root.GetProperty("reasoning").GetString() ?? "",
|
|||
|
|
AIProvider = "llm-api-v2",
|
|||
|
|
DetectionLayer = "LLM",
|
|||
|
|
ScanTime = DateTime.Now
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
catch
|
|||
|
|
{
|
|||
|
|
return new ScanResult
|
|||
|
|
{
|
|||
|
|
Pid = proc.Pid,
|
|||
|
|
ProcessName = proc.Name,
|
|||
|
|
RiskLevel = RiskLevel.Safe,
|
|||
|
|
RiskScore = 0,
|
|||
|
|
Reasoning = content,
|
|||
|
|
AIProvider = "llm-api-v2",
|
|||
|
|
DetectionLayer = "LLM",
|
|||
|
|
ScanTime = DateTime.Now
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task<(bool ok, string message)> TestConnectionAsync()
|
|||
|
|
{
|
|||
|
|
if (_config.Provider == AIProvider.Heuristic)
|
|||
|
|
{
|
|||
|
|
return _config.EnableLocalAI
|
|||
|
|
? (true, "AI 管线模式 (Pipeline v2.0, 无需 API)")
|
|||
|
|
: (true, "启发式规则模式 (Heuristic v2.0, 无需 API)");
|
|||
|
|
}
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
await CallAIAsync("ping");
|
|||
|
|
return (true, $"OK - {_config.Provider}/{_config.Model}");
|
|||
|
|
}
|
|||
|
|
catch (Exception ex) { return (false, ex.Message); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private const string V2SystemPrompt = @"You are a Windows memory security expert (v2.0) analyzing process threat levels.
|
|||
|
|
|
|||
|
|
=== CORE PRINCIPLE ===
|
|||
|
|
The VAST majority (>95%) of processes are SAFE. Default to ""safe"" unless you see CLEAR threat evidence.
|
|||
|
|
|
|||
|
|
=== 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.
|
|||
|
|
|
|||
|
|
=== RISK LEVELS ===
|
|||
|
|
""safe"": Normal benign process, even with minor deviations (< 0.30)
|
|||
|
|
""suspicious"": Multiple weak signals or single strong signal (0.30-0.70)
|
|||
|
|
""high"": Clear evidence with multiple concurring signals (> 0.70)
|
|||
|
|
|
|||
|
|
=== JSON RESPONSE ===
|
|||
|
|
{""risk_level"":""safe"",""risk_score"":0.0,""indicators"":[],""reasoning"":""""}";
|
|||
|
|
}
|