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 { { "process_names", new object[] { "IMPlatformClient", "IMPP.ISphere", "iSphere", "importal" } }, { "include_modules", true }, { "max_modules", 180 } }; Dictionary runtime = RuntimeProbe.ProbeClientRuntime(probeArgs); WriteJson(Path.Combine(outputDir, "probe_client_runtime.json"), runtime); Dictionary processDetails = LiveProbeInventory.CollectProcessDetails(runtime); WriteJson(Path.Combine(outputDir, "process_details.json"), processDetails); Dictionary windows = WindowScanner.Scan(new Dictionary { { "include_all_visible", true } }); WriteJson(Path.Combine(outputDir, "scan_windows.json"), windows); Dictionary network = LiveProbeInventory.CollectNetwork(runtime); WriteJson(Path.Combine(outputDir, "network_inventory.json"), network); Dictionary servicesRegistryShortcuts = LiveProbeInventory.CollectServicesRegistryShortcuts(); WriteJson(Path.Combine(outputDir, "services_registry_shortcuts.json"), servicesRegistryShortcuts); Dictionary filesystem = LiveProbeInventory.CollectFilesystem(runtime); WriteJson(Path.Combine(outputDir, "filesystem_inventory.json"), filesystem); Dictionary sendEntrypoints = SendConnectorPreflight.ProbeSendEntrypoints(new Dictionary()); WriteJson(Path.Combine(outputDir, "send_entrypoints_preflight.json"), sendEntrypoints); string uiaDir = Path.Combine(outputDir, "uia"); Directory.CreateDirectory(uiaDir); List> uiaSummary = DumpTargetWindowUia(runtime, windows, uiaDir); WriteJson(Path.Combine(outputDir, "uia_summary.json"), uiaSummary); List> sendUiaPreflight = ProbeTargetWindowSendControls(runtime, windows); WriteJson(Path.Combine(outputDir, "send_uia_controls_preflight.json"), sendUiaPreflight); Dictionary 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、send_entrypoints_preflight.json、send_uia_controls_preflight.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> ProbeTargetWindowSendControls(Dictionary runtime, Dictionary windows) { var targetPids = TargetPids(runtime); var summary = new List>(); object rawWindows; if (!windows.TryGetValue("windows", out rawWindows)) { return summary; } var windowList = rawWindows as List>; if (windowList == null) { return summary; } foreach (Dictionary window in windowList) { int pid = GetInt(window, "pid"); if (!targetPids.Contains(pid)) { continue; } string hwnd = GetString(window, "hwnd"); string title = GetString(window, "title"); Dictionary probe = SendConnectorPreflight.ProbeSendUiaControls("live-probe-send-uia", "probe_send_uia_controls", new Dictionary { { "hwnd", hwnd }, { "max_depth", 8 }, { "max_children", 180 } }); summary.Add(new Dictionary { { "pid", pid }, { "hwnd", hwnd }, { "title_present", !string.IsNullOrEmpty(title) }, { "ok", GetBool(probe, "ok") }, { "probe", probe } }); } return summary; } private static List> DumpTargetWindowUia(Dictionary runtime, Dictionary windows, string uiaDir) { var targetPids = TargetPids(runtime); var summary = new List>(); object rawWindows; if (!windows.TryGetValue("windows", out rawWindows)) { return summary; } var windowList = rawWindows as List>; if (windowList == null) { return summary; } foreach (Dictionary 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 dump = UiaDumper.Dump("live-probe-uia", "dump_uia", new Dictionary { { "hwnd", hwnd }, { "max_depth", 8 }, { "include_text", true }, { "max_children", 180 } }); WriteJson(outPath, dump); summary.Add(new Dictionary { { "pid", pid }, { "hwnd", hwnd }, { "title", title }, { "path", outPath }, { "ok", GetBool(dump, "ok") } }); } return summary; } private static HashSet TargetPids(Dictionary runtime) { var pids = new HashSet(); object rawTargets; if (!runtime.TryGetValue("targets", out rawTargets)) { return pids; } var targets = rawTargets as List>; if (targets == null) { return pids; } foreach (Dictionary target in targets) { int pid = GetInt(target, "pid"); if (pid > 0) { pids.Add(pid); } } return pids; } private static Dictionary Manifest(string runId, string outputDir) { return new Dictionary { { "recorder_name", "ISphereLiveProbeRecorder" }, { "recorder_version", "0.3.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 source, string key) { object value; if (source.TryGetValue(key, out value) && value != null) { return Convert.ToString(value); } return ""; } private static int GetInt(Dictionary 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 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); } } }