Files
isphere-ai-bridge/native/ISphereWinHelper/LiveProbeRecorder.cs
2026-07-10 10:59:48 +08:00

260 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace ISphereWinHelper
{
internal static class LiveProbeRecorder
{
public static int Run(string[] args)
{
Console.OutputEncoding = Encoding.UTF8;
string outputRoot = ResolveOutputRoot(args);
string runId = DateTime.Now.ToString("yyyyMMdd-HHmmss");
string outputDir = Path.Combine(outputRoot, "isphere-live-probe-" + runId);
Directory.CreateDirectory(outputDir);
try
{
WriteText(Path.Combine(outputDir, "README.txt"), Readme());
WriteJson(Path.Combine(outputDir, "manifest.json"), Manifest(runId, outputDir));
var probeArgs = new Dictionary<string, object>
{
{ "process_names", new object[] { "IMPlatformClient", "IMPP.ISphere", "iSphere", "importal" } },
{ "include_modules", true },
{ "max_modules", 180 }
};
Dictionary<string, object> runtime = RuntimeProbe.ProbeClientRuntime(probeArgs);
WriteJson(Path.Combine(outputDir, "probe_client_runtime.json"), runtime);
Dictionary<string, object> processDetails = LiveProbeInventory.CollectProcessDetails(runtime);
WriteJson(Path.Combine(outputDir, "process_details.json"), processDetails);
Dictionary<string, object> windows = WindowScanner.Scan(new Dictionary<string, object>
{
{ "include_all_visible", true }
});
WriteJson(Path.Combine(outputDir, "scan_windows.json"), windows);
Dictionary<string, object> network = LiveProbeInventory.CollectNetwork(runtime);
WriteJson(Path.Combine(outputDir, "network_inventory.json"), network);
Dictionary<string, object> servicesRegistryShortcuts = LiveProbeInventory.CollectServicesRegistryShortcuts();
WriteJson(Path.Combine(outputDir, "services_registry_shortcuts.json"), servicesRegistryShortcuts);
Dictionary<string, object> filesystem = LiveProbeInventory.CollectFilesystem(runtime);
WriteJson(Path.Combine(outputDir, "filesystem_inventory.json"), filesystem);
string uiaDir = Path.Combine(outputDir, "uia");
Directory.CreateDirectory(uiaDir);
List<Dictionary<string, object>> uiaSummary = DumpTargetWindowUia(runtime, windows, uiaDir);
WriteJson(Path.Combine(outputDir, "uia_summary.json"), uiaSummary);
Dictionary<string, object> feasibility = LiveProbeInventory.SummarizeFeasibility(runtime, filesystem, network);
WriteJson(Path.Combine(outputDir, "feasibility_summary.json"), feasibility);
WriteText(Path.Combine(outputDir, "NEXT_STEP.txt"),
"把整个目录压缩后发回即可。\r\n" +
"重点文件probe_client_runtime.json、process_details.json、network_inventory.json、filesystem_inventory.json、services_registry_shortcuts.json、scan_windows.json、uia_summary.json、uia/*.json、feasibility_summary.json\r\n");
Console.WriteLine("OK");
Console.WriteLine("已生成采集目录:");
Console.WriteLine(outputDir);
Console.WriteLine();
Console.WriteLine("请把这个目录压缩后发回。");
Pause(args);
return 0;
}
catch (Exception ex)
{
WriteText(Path.Combine(outputDir, "RECORDER_ERROR.txt"), ex.ToString());
Console.WriteLine("FAILED");
Console.WriteLine(ex.ToString());
Pause(args);
return 1;
}
}
private static List<Dictionary<string, object>> DumpTargetWindowUia(Dictionary<string, object> runtime, Dictionary<string, object> windows, string uiaDir)
{
var targetPids = TargetPids(runtime);
var summary = new List<Dictionary<string, object>>();
object rawWindows;
if (!windows.TryGetValue("windows", out rawWindows))
{
return summary;
}
var windowList = rawWindows as List<Dictionary<string, object>>;
if (windowList == null)
{
return summary;
}
foreach (Dictionary<string, object> window in windowList)
{
int pid = GetInt(window, "pid");
if (!targetPids.Contains(pid))
{
continue;
}
string hwnd = GetString(window, "hwnd");
string title = GetString(window, "title");
string fileName = "uia-pid" + pid + "-" + hwnd.Replace("0x", "") + ".json";
string outPath = Path.Combine(uiaDir, fileName);
Dictionary<string, object> dump = UiaDumper.Dump("live-probe-uia", "dump_uia", new Dictionary<string, object>
{
{ "hwnd", hwnd },
{ "max_depth", 8 },
{ "include_text", true },
{ "max_children", 180 }
});
WriteJson(outPath, dump);
summary.Add(new Dictionary<string, object>
{
{ "pid", pid },
{ "hwnd", hwnd },
{ "title", title },
{ "path", outPath },
{ "ok", GetBool(dump, "ok") }
});
}
return summary;
}
private static HashSet<int> TargetPids(Dictionary<string, object> runtime)
{
var pids = new HashSet<int>();
object rawTargets;
if (!runtime.TryGetValue("targets", out rawTargets))
{
return pids;
}
var targets = rawTargets as List<Dictionary<string, object>>;
if (targets == null)
{
return pids;
}
foreach (Dictionary<string, object> target in targets)
{
int pid = GetInt(target, "pid");
if (pid > 0)
{
pids.Add(pid);
}
}
return pids;
}
private static Dictionary<string, object> Manifest(string runId, string outputDir)
{
return new Dictionary<string, object>
{
{ "recorder_name", "ISphereLiveProbeRecorder" },
{ "recorder_version", "0.2.0" },
{ "helper_protocol", HelperProtocol.Protocol },
{ "run_id", runId },
{ "timestamp_local", DateTime.Now.ToString("o") },
{ "timestamp_utc", DateTime.UtcNow.ToString("o") },
{ "output_dir", outputDir },
{ "scope", "read_only_full_inventory_for_B_route_sidecar_and_A_route_rpa_fallback" },
{ "does_not", new object[] { "send_message", "send_file", "upload_file", "click_ui", "type_text", "inject_hook", "modify_client_data" } },
{ "machine_name", Environment.MachineName },
{ "user_domain", Environment.UserDomainName },
{ "user_name", Environment.UserName },
{ "os_version", Environment.OSVersion.ToString() },
{ "is_64_bit_os", Environment.Is64BitOperatingSystem },
{ "is_64_bit_process", Environment.Is64BitProcess },
{ "clr_version", Environment.Version.ToString() }
};
}
private static string Readme()
{
return "ISphereLiveProbeRecorder\r\n\r\n" +
"使用方式:\r\n" +
"1. 到可以正常连接服务端的环境。\r\n" +
"2. 手工打开并登录 iSphere / IMPlatformClient。\r\n" +
"3. 尽量手工打开一个联系人聊天窗口、一个群聊窗口,并让输入框/发送按钮/附件按钮所在区域可见;不用发送任何内容。\r\n" +
"4. 保持客户端窗口打开。\r\n" +
"5. 双击 ISphereLiveProbeRecorder.exe。\r\n" +
"6. 程序会在 probe-output 下生成 isphere-live-probe-时间戳 目录。\r\n" +
"7. 把整个目录压缩后发回。\r\n\r\n" +
"采集内容:运行进程、模块、命令行摘要、窗口、目标窗口 UIA 控件树、安装目录关键文件、配置/日志/数据库候选、注册表卸载项、快捷方式、服务、本地 TCP 连接、命名管道。\r\n" +
"不会执行发送消息、发送文件、上传文件、点击、输入、注入、hook、修改客户端数据。\r\n";
}
private static string ResolveOutputRoot(string[] args)
{
for (int i = 0; i < args.Length - 1; i++)
{
if (string.Equals(args[i], "--out", StringComparison.OrdinalIgnoreCase))
{
return Path.GetFullPath(args[i + 1]);
}
}
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "probe-output");
}
private static void Pause(string[] args)
{
foreach (string arg in args)
{
if (string.Equals(arg, "--no-pause", StringComparison.OrdinalIgnoreCase))
{
return;
}
}
if (Environment.UserInteractive)
{
Console.WriteLine();
Console.WriteLine("按回车退出...");
try { Console.ReadLine(); } catch { }
}
}
private static string GetString(Dictionary<string, object> source, string key)
{
object value;
if (source.TryGetValue(key, out value) && value != null)
{
return Convert.ToString(value);
}
return "";
}
private static int GetInt(Dictionary<string, object> source, string key)
{
object value;
if (source.TryGetValue(key, out value) && value != null)
{
try { return Convert.ToInt32(value); } catch { }
}
return 0;
}
private static bool GetBool(Dictionary<string, object> source, string key)
{
object value;
if (source.TryGetValue(key, out value) && value != null)
{
try { return Convert.ToBoolean(value); } catch { }
}
return false;
}
private static void WriteJson(string path, object value)
{
File.WriteAllText(path, HelperProtocol.ToJson(value), Encoding.UTF8);
}
private static void WriteText(string path, string value)
{
File.WriteAllText(path, value, Encoding.UTF8);
}
}
}