dsh-perm-gate 5.0.4 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/CHANGELOG.ja.md +0 -2
  2. package/CHANGELOG.ko.md +0 -2
  3. package/CHANGELOG.md +4 -2
  4. package/INSTALL.ja.md +0 -15
  5. package/INSTALL.ko.md +0 -15
  6. package/INSTALL.md +0 -15
  7. package/INSTALL.zh.md +0 -15
  8. package/README.ja.md +6 -18
  9. package/README.ko.md +6 -18
  10. package/README.md +40 -19
  11. package/README.zh.md +34 -18
  12. package/assets/appcontainer-launcher.cs +703 -0
  13. package/dsh.plugin.json +2 -2
  14. package/lib/appcontainer/compile.d.ts +17 -0
  15. package/lib/appcontainer/compile.js +74 -0
  16. package/lib/appcontainer/exempt.d.ts +18 -0
  17. package/lib/appcontainer/exempt.js +71 -0
  18. package/lib/appcontainer/index.d.ts +85 -0
  19. package/lib/appcontainer/index.js +286 -0
  20. package/lib/appcontainer/probe.d.ts +44 -0
  21. package/lib/appcontainer/probe.js +66 -0
  22. package/lib/appcontainer/sid.d.ts +23 -0
  23. package/lib/appcontainer/sid.js +33 -0
  24. package/lib/appcontainer/wrap.d.ts +22 -0
  25. package/lib/appcontainer/wrap.js +40 -0
  26. package/lib/cli.js +0 -0
  27. package/lib/client.js +1425 -1108
  28. package/lib/client.js.map +1 -1
  29. package/lib/config.d.ts +41 -1
  30. package/lib/config.js +6 -0
  31. package/lib/index.d.ts +1 -1
  32. package/lib/index.js +53 -0
  33. package/lib/runtime.d.ts +22 -0
  34. package/lib/runtime.js +24 -0
  35. package/locale/de.json +1 -0
  36. package/locale/en.json +1 -0
  37. package/locale/es.json +1 -0
  38. package/locale/fr.json +1 -0
  39. package/locale/it.json +1 -0
  40. package/locale/ja.json +1 -0
  41. package/locale/ko.json +1 -0
  42. package/locale/ru.json +1 -0
  43. package/locale/zh.json +1 -0
  44. package/package.json +28 -28
  45. package/scripts/patch-permission-glyph.mjs +0 -0
  46. package/README.de.md +0 -487
  47. package/README.es.md +0 -489
  48. package/README.fr.md +0 -498
  49. package/README.it.md +0 -490
  50. package/README.ru.md +0 -478
@@ -0,0 +1,703 @@
1
+ /**
2
+ * appcontainer-launcher.cs — dsh-perm-gate 的 Windows AppContainer 启动器。
3
+ *
4
+ * 职责(单一):把一条 shell 命令放进 AppContainer 里执行——
5
+ * 1. 对 workspace / temp 做幂等 ACE 授予(容器 SID 默认零环境访问,系统目录
6
+ * 靠 ALL APPLICATION PACKAGES 默认 ACE,工作区必须显式开);
7
+ * 2. 容器 SID 由 OS 派生(Region B 三级解析链:apiset→classic→moniker;
8
+ * 自造 S-1-15-2 SID 一律被内核 STATUS_INVALID_SID 拒收);随后走官方
9
+ * 属性列表路径(PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES +
10
+ * CreateProcessW),**能力清单为空** → WFP 层直接封死全部非回环出网,
11
+ * 回环经 CheckNetIsolation 豁免放行,出网只能走注入的本地过滤代理;
12
+ * 3. cmd.exe /d /s /c "<解码后命令>" 在容器内执行,stdio 继承、退出码透传;
13
+ * 4. 挂 kill-on-close Job,启动器被杀则整棵子进程树陪葬。
14
+ *
15
+ * 动词(Region E 分发):
16
+ * --print-sid stdout 单行容器 SID(Node 侧依赖,协议冻结)
17
+ * --probe stdout 单行 JSON 裁决(恒 exit 0;仅诊断归因用,
18
+ * 绝非放行依据——逐 launch 解析才是信任锚)
19
+ * --register [--sig N] 显式注册动词(AppContainerRegisterSid 签名未
20
+ * 公开,候选表试错打印 hr;主流程绝不调用)
21
+ * --unregister [--sig N] 镜像清理(hunt 试错后的恢复路径)
22
+ * --config <f> --exec-b64 <b64> 执行(契约不变)
23
+ *
24
+ * 环境继承形态(windows-acl 验证过的):启动器先改**自身**进程环境
25
+ * (TMP/TEMP/代理变量),CreateProcessW 传 NULL env block 让子进程继承
26
+ * ——不碰显式 env block 封送(同族代码在该路径上翻过车)。
27
+ *
28
+ * 约定:启动失败一律 stderr 打 `dsh-perm-gate-appcontainer: <code>: <msg>`
29
+ * 并以 125 退出(runner-failure 签名,供门控结果审计归因);命令本身的失败
30
+ * 按子进程退出码原样透传。探针例外:--probe 任何子项失败都进 JSON、恒 exit 0。
31
+ *
32
+ * 语言等级:.NET Framework 自带 csc 只有 C# 5——不许用字符串插值、?. 等新语法。
33
+ * 编译:csc /nologo /target:exe /optimize+ /r:System.Web.Extensions.dll
34
+ * /out:launcher.exe appcontainer-launcher.cs
35
+ */
36
+ using System;
37
+ using System.Collections.Generic;
38
+ using System.Diagnostics;
39
+ using System.IO;
40
+ using System.Runtime.InteropServices;
41
+ using System.Security.AccessControl;
42
+ using System.Security.Principal;
43
+
44
+ namespace DshPermGate
45
+ {
46
+ internal static class Launcher
47
+ {
48
+ const int EXIT_LAUNCH_FAILURE = 125;
49
+ const string SIGNATURE = "dsh-perm-gate-appcontainer: ";
50
+ const string CONTAINER_NAME = "dsh-perm-gate.ac";
51
+
52
+ // ── Win32 ──────────────────────────────────────────────────────────
53
+ const uint JobObjectExtendedLimitInformation = 9;
54
+ const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000;
55
+ const uint STARTF_USESTDHANDLES = 0x0100;
56
+ const uint HANDLE_FLAG_INHERIT = 0x1;
57
+ // 容器内启动统一走官方属性列表路径(kernel 侧自建 token):
58
+ // PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES + CreateProcessW。
59
+ const uint PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES = 0x00020009;
60
+ const uint EXTENDED_STARTUPINFO_PRESENT = 0x00080000;
61
+
62
+ [StructLayout(LayoutKind.Sequential)]
63
+ struct JOBOBJECT_BASIC_LIMIT_INFORMATION
64
+ {
65
+ public long PerProcessUserTimeLimit;
66
+ public long PerJobUserTimeLimit;
67
+ public uint LimitFlags;
68
+ public UIntPtr MinimumWorkingSetSize;
69
+ public UIntPtr MaximumWorkingSetSize;
70
+ public uint ActiveProcessLimit;
71
+ public UIntPtr Affinity;
72
+ public uint PriorityClass;
73
+ public uint SchedulingClass;
74
+ }
75
+
76
+ [StructLayout(LayoutKind.Sequential)]
77
+ struct IO_COUNTERS
78
+ {
79
+ public ulong ReadOperationCount;
80
+ public ulong WriteOperationCount;
81
+ public ulong OtherOperationCount;
82
+ public ulong ReadTransferCount;
83
+ public ulong WriteTransferCount;
84
+ public ulong OtherTransferCount;
85
+ }
86
+
87
+ [StructLayout(LayoutKind.Sequential)]
88
+ struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
89
+ {
90
+ public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
91
+ public IO_COUNTERS IoInfo;
92
+ public UIntPtr ProcessMemoryLimit;
93
+ public UIntPtr JobMemoryLimit;
94
+ public UIntPtr PeakProcessMemoryUsed;
95
+ public UIntPtr PeakJobMemoryUsed;
96
+ }
97
+
98
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
99
+ struct STARTUPINFOW
100
+ {
101
+ public uint cb;
102
+ public string lpReserved;
103
+ public string lpDesktop;
104
+ public string lpTitle;
105
+ public uint dwX;
106
+ public uint dwY;
107
+ public uint dwXSize;
108
+ public uint dwYSize;
109
+ public uint dwXCountChars;
110
+ public uint dwYCountChars;
111
+ public uint dwFillAttribute;
112
+ public uint dwFlags;
113
+ public ushort wShowWindow;
114
+ public ushort cbReserved2;
115
+ public IntPtr lpReserved2;
116
+ public IntPtr hStdInput;
117
+ public IntPtr hStdOutput;
118
+ public IntPtr hStdError;
119
+ }
120
+
121
+ [StructLayout(LayoutKind.Sequential)]
122
+ struct STARTUPINFOEXW
123
+ {
124
+ public STARTUPINFOW StartupInfo;
125
+ public IntPtr lpAttributeList;
126
+ }
127
+
128
+ [StructLayout(LayoutKind.Sequential)]
129
+ struct SECURITY_CAPABILITIES
130
+ {
131
+ public IntPtr Capabilities; // PSID_AND_ATTRIBUTES (zero = no capabilities)
132
+ public uint CapabilityCount;
133
+ public IntPtr AppContainerSid; // PSID
134
+ }
135
+
136
+ [StructLayout(LayoutKind.Sequential)]
137
+ struct PROCESS_INFORMATION
138
+ {
139
+ public IntPtr hProcess;
140
+ public IntPtr hThread;
141
+ public uint dwProcessId;
142
+ public uint dwThreadId;
143
+ }
144
+
145
+ [DllImport("kernel32.dll", SetLastError = true)]
146
+ static extern IntPtr CreateJobObjectW(IntPtr lpJobAttributes, string lpName);
147
+
148
+ [DllImport("kernel32.dll", SetLastError = true)]
149
+ static extern bool SetInformationJobObject(IntPtr hJob, uint infoClass, IntPtr info, uint length);
150
+
151
+ [DllImport("kernel32.dll", SetLastError = true)]
152
+ static extern bool AssignProcessToJobObject(IntPtr hJob, IntPtr hProcess);
153
+
154
+ [DllImport("kernel32.dll", SetLastError = true)]
155
+ static extern bool CloseHandle(IntPtr handle);
156
+
157
+ [DllImport("kernel32.dll", SetLastError = true)]
158
+ static extern bool SetHandleInformation(IntPtr handle, uint mask, uint flags);
159
+
160
+ [DllImport("kernel32.dll", SetLastError = true)]
161
+ static extern bool GetExitCodeProcess(IntPtr hProcess, out uint exitCode);
162
+
163
+ [DllImport("kernel32.dll", SetLastError = true)]
164
+ static extern void DeleteProcThreadAttributeList(IntPtr list);
165
+
166
+ [DllImport("kernel32.dll", SetLastError = true)]
167
+ static extern bool InitializeProcThreadAttributeList(IntPtr list, int count, int flags, ref IntPtr size);
168
+
169
+ [DllImport("kernel32.dll", SetLastError = true)]
170
+ static extern bool UpdateProcThreadAttribute(
171
+ IntPtr list, uint flags, IntPtr attribute, IntPtr value, UIntPtr size, IntPtr prevValue, IntPtr returnValue);
172
+
173
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
174
+ static extern bool CreateProcessW(
175
+ string lpApplicationName, string lpCommandLine,
176
+ IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles,
177
+ uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory,
178
+ STARTUPINFOEXW lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
179
+
180
+ [DllImport("kernel32.dll", SetLastError = true)]
181
+ static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
182
+
183
+ [DllImport("kernel32.dll", SetLastError = true)]
184
+ static extern IntPtr GetStdHandle(int handle);
185
+
186
+ [DllImport("advapi32.dll", CharSet = CharSet.Unicode)]
187
+ static extern long ConvertSidToStringSidW(IntPtr sid, out IntPtr stringSid);
188
+
189
+ [DllImport("kernel32.dll", SetLastError = true)]
190
+ static extern IntPtr LocalFree(IntPtr handle);
191
+
192
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
193
+ static extern IntPtr GetProcAddress(IntPtr module, string name);
194
+
195
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
196
+ static extern IntPtr LoadLibraryW(string name);
197
+
198
+ static void Fail(string code, string message)
199
+ {
200
+ Console.Error.WriteLine(SIGNATURE + code + ": " + message);
201
+ Environment.Exit(EXIT_LAUNCH_FAILURE);
202
+ }
203
+
204
+ // ══ Region B: DerivationBackend(两代分叉的唯一区段) ═══════════════
205
+ // 判据:grep 全文件,"DeriveAppContainerSid|Moniker" 字样只许出现在
206
+ // 本 Region。三级解析链序固定:apiset(≤24H2 契约面)→ KernelBase
207
+ // 经典名 → moniker 名(KernelBase → kernel.appcore)。lookup/register/
208
+ // unregister/createToken 一律从**同一命中模块**解析(单 provider,
209
+ // 防混搭 ABI)。
210
+
211
+ /// apiset 契约模块(经典函数的官方稳定面;26200 上已无这些 DLL,
212
+ /// 逐个 LoadLibrary 落空即瞬间进下一级——尽力而为,绝不致命)。
213
+ static readonly string[] ApiSetModules = new string[] {
214
+ "api-ms-win-appmodel-runtime-l1-1-5.dll",
215
+ "api-ms-win-appmodel-runtime-l1-1-4.dll",
216
+ "api-ms-win-appmodel-runtime-l1-1-3.dll",
217
+ "api-ms-win-appmodel-runtime-l1-1-2.dll",
218
+ "api-ms-win-appmodel-runtime-l1-1-1.dll",
219
+ "api-ms-win-appmodel-runtime-l1-1-0.dll",
220
+ };
221
+ /// 经典派生导出名(注意:MS 文档别名 "DeriveAppContainerSidFromName"
222
+ /// 不是导出名;导出名带 AppContainerName 后缀,EntryPoint 显式)。
223
+ const string ClassicDeriveExport = "DeriveAppContainerSidFromAppContainerName";
224
+ const string MonikerDeriveExport = "AppContainerDeriveSidFromMoniker";
225
+ const string LookupExport = "AppContainerLookupMoniker";
226
+ const string RegisterExport = "AppContainerRegisterSid";
227
+ const string UnregisterExport = "AppContainerUnregisterSid";
228
+ const string CreateTokenExport = "CreateAppContainerToken";
229
+
230
+ /// One resolved provider: everything era-specific lives here.
231
+ struct Backend
232
+ {
233
+ public string Era; // "classic" | "moniker" | "none"
234
+ public string ModuleName; // provider module file name
235
+ public string DeriveName; // derive export name (for diagnostics)
236
+ public IntPtr Derive; // (PCWSTR, out PSID) → HRESULT
237
+ public IntPtr Lookup; // AppContainerLookupMoniker(可缺失)
238
+ public IntPtr Register; // AppContainerRegisterSid(可缺失)
239
+ public IntPtr Unregister; // AppContainerUnregisterSid(可缺失)
240
+ public IntPtr CreateToken; // CreateAppContainerToken(可缺失)
241
+ }
242
+
243
+ delegate long DeriveDelegate(string name, out IntPtr sid);
244
+ delegate long LookupDelegate(IntPtr sid, out IntPtr monikerPtr);
245
+
246
+ /// 注册签名候选表(AppContainerRegisterSid 无公开文档;hunt 协议逐个
247
+ /// 试错,判据三连 = hr==0 → Mappings 新增键 → 试启动 87 消失)。
248
+ /// --sig N 选择候选(默认 0);崩溃/异常按该候选失败记录,换 N 重试。
249
+ delegate long RegisterSig0(IntPtr sid, string moniker);
250
+ delegate long RegisterSig1(string moniker, IntPtr sid);
251
+ delegate long RegisterSig2(string moniker, IntPtr sid, uint flags);
252
+ delegate long RegisterSig3(IntPtr sid, string moniker, string displayName);
253
+ const int RegisterSigCount = 4;
254
+
255
+ delegate long UnregisterSig0(IntPtr sid);
256
+ delegate long UnregisterSig1(string moniker);
257
+ delegate long UnregisterSig2(IntPtr sid, string moniker);
258
+ const int UnregisterSigCount = 3;
259
+
260
+ /// CreateAppContainerToken 候选形(仅探针用:不建进程判 SID 可用性,
261
+ /// 把「注册失败」与「token 拒收」切开;启动路径绝不经过它)。
262
+ delegate long CreateTokenDelegate(IntPtr sid, uint capabilityCount, IntPtr capabilities, out IntPtr token);
263
+
264
+ static Backend ResolveBackend()
265
+ {
266
+ var b = new Backend();
267
+ // Level-0: apiset 契约模块 + 经典名
268
+ foreach (var apiSet in ApiSetModules)
269
+ {
270
+ var m = LoadLibraryW(apiSet);
271
+ if (m == IntPtr.Zero) continue;
272
+ var p = GetProcAddress(m, ClassicDeriveExport);
273
+ if (p != IntPtr.Zero) return BuildBackend(m, apiSet, p, "classic", ClassicDeriveExport);
274
+ }
275
+ var kb = LoadLibraryW("KernelBase.dll");
276
+ if (kb != IntPtr.Zero)
277
+ {
278
+ // Level-1: KernelBase 经典名(Win8…24H2 的宿主)
279
+ var classic = GetProcAddress(kb, ClassicDeriveExport);
280
+ if (classic != IntPtr.Zero) return BuildBackend(kb, "KernelBase.dll", classic, "classic", ClassicDeriveExport);
281
+ // Level-2: moniker 名(25H2+ 新代;经典导出已消失)
282
+ var moniker = GetProcAddress(kb, MonikerDeriveExport);
283
+ if (moniker != IntPtr.Zero) return BuildBackend(kb, "KernelBase.dll", moniker, "moniker", MonikerDeriveExport);
284
+ }
285
+ var kac = LoadLibraryW("kernel.appcore.dll");
286
+ if (kac != IntPtr.Zero)
287
+ {
288
+ var moniker2 = GetProcAddress(kac, MonikerDeriveExport);
289
+ if (moniker2 != IntPtr.Zero) return BuildBackend(kac, "kernel.appcore.dll", moniker2, "moniker", MonikerDeriveExport);
290
+ }
291
+ return b; // Era == null → 调用方视为 "none"
292
+ }
293
+
294
+ static Backend BuildBackend(IntPtr module, string moduleName, IntPtr derive, string era, string deriveName)
295
+ {
296
+ var b = new Backend();
297
+ b.Era = era;
298
+ b.ModuleName = moduleName;
299
+ b.DeriveName = deriveName;
300
+ b.Derive = derive;
301
+ b.Lookup = GetProcAddress(module, LookupExport);
302
+ b.Register = GetProcAddress(module, RegisterExport);
303
+ b.Unregister = GetProcAddress(module, UnregisterExport);
304
+ b.CreateToken = GetProcAddress(module, CreateTokenExport);
305
+ return b;
306
+ }
307
+
308
+ /// era 无关的派生调用(两代共用 (PCWSTR, out PSID)→HRESULT 形)。
309
+ static long CallDerive(Backend b, out IntPtr sid)
310
+ {
311
+ var fn = (DeriveDelegate)Marshal.GetDelegateForFunctionPointer(b.Derive, typeof(DeriveDelegate));
312
+ return fn(CONTAINER_NAME, out sid);
313
+ }
314
+
315
+ // ══ Region C: ACL + env(era 中立) ════════════════════════════════
316
+
317
+ static bool HasRights(DirectorySecurity ac, SecurityIdentifier sid, FileSystemRights rights)
318
+ {
319
+ foreach (FileSystemAccessRule rule in ac.GetAccessRules(true, true, typeof(SecurityIdentifier)))
320
+ {
321
+ if (!sid.Equals(rule.IdentityReference)) continue;
322
+ if (rule.AccessControlType != AccessControlType.Allow) continue;
323
+ if ((rule.InheritanceFlags & InheritanceFlags.ContainerInherit) == 0) continue;
324
+ if ((rule.FileSystemRights & rights) == rights) return true;
325
+ }
326
+ return false;
327
+ }
328
+
329
+ static void GrantTree(string directory, SecurityIdentifier sid, FileSystemRights rights)
330
+ {
331
+ if (!Directory.Exists(directory)) throw new IOException("directory does not exist: " + directory);
332
+ var info = new DirectoryInfo(directory);
333
+ var ac = info.GetAccessControl();
334
+ if (HasRights(ac, sid, rights)) return; // idempotent: standing ACE stays, no re-propagation
335
+ var rule = new FileSystemAccessRule(
336
+ sid, rights,
337
+ InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
338
+ PropagationFlags.None, AccessControlType.Allow);
339
+ ac.AddAccessRule(rule);
340
+ info.SetAccessControl(ac);
341
+ }
342
+
343
+ // ══ Region D: 属性列表启动(唯一启动路径,era 中立) ══════════════
344
+
345
+ struct LaunchOutcome
346
+ {
347
+ public bool Started; // CreateProcessW succeeded
348
+ public int Win32Err; // CreateProcessW 层错误码(87/4250/5/2…)
349
+ public int ChildExit; // 子进程退出码(Started 时有效)
350
+ }
351
+
352
+ /// 完整启动路径:ACL → env 自改继承 → 属性表(零能力)→ cmd /d /s /c。
353
+ /// 探针与 exec 共用;探针传哨兵命令与一次性目录。
354
+ static LaunchOutcome TryLaunch(SecurityIdentifier containerSid, string workspace, string tempDir,
355
+ string mode, IDictionary<string, string> proxyEnv, string command)
356
+ {
357
+ var outcome = new LaunchOutcome();
358
+ outcome.Started = false;
359
+ outcome.Win32Err = 0;
360
+ outcome.ChildExit = -1;
361
+ try
362
+ {
363
+ Directory.CreateDirectory(tempDir);
364
+ GrantTree(workspace, containerSid, FileSystemRights.ReadAndExecute);
365
+ GrantTree(tempDir, containerSid, FileSystemRights.FullControl);
366
+ if (mode == "workspace-write") GrantTree(workspace, containerSid, FileSystemRights.Modify | FileSystemRights.ExecuteFile);
367
+ }
368
+ catch (Exception) { outcome.Win32Err = 5; return outcome; } // ACL 面失败:按拒绝访问归因
369
+
370
+ try
371
+ {
372
+ Environment.SetEnvironmentVariable("TMP", tempDir, EnvironmentVariableTarget.Process);
373
+ Environment.SetEnvironmentVariable("TEMP", tempDir, EnvironmentVariableTarget.Process);
374
+ if (proxyEnv != null)
375
+ {
376
+ foreach (KeyValuePair<string, string> kv in proxyEnv)
377
+ Environment.SetEnvironmentVariable(kv.Key, kv.Value, EnvironmentVariableTarget.Process);
378
+ }
379
+ }
380
+ catch (Exception) { outcome.Win32Err = 5; return outcome; }
381
+
382
+ // 容器 SID 二进制 + 零能力属性表:kernel 侧自建 AppContainer token。
383
+ IntPtr sidPtr = Marshal.AllocHGlobal(containerSid.BinaryLength);
384
+ var sidBytes = new byte[containerSid.BinaryLength];
385
+ containerSid.GetBinaryForm(sidBytes, 0);
386
+ Marshal.Copy(sidBytes, 0, sidPtr, sidBytes.Length);
387
+
388
+ var secCaps = new SECURITY_CAPABILITIES();
389
+ secCaps.AppContainerSid = sidPtr;
390
+ secCaps.CapabilityCount = 0;
391
+ secCaps.Capabilities = IntPtr.Zero;
392
+ IntPtr attrSize = IntPtr.Zero;
393
+ InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref attrSize);
394
+ var attrList = Marshal.AllocHGlobal(attrSize);
395
+ if (!InitializeProcThreadAttributeList(attrList, 1, 0, ref attrSize))
396
+ {
397
+ outcome.Win32Err = Marshal.GetLastWin32Error();
398
+ Marshal.FreeHGlobal(attrList); Marshal.FreeHGlobal(sidPtr);
399
+ return outcome;
400
+ }
401
+ var secCapsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SECURITY_CAPABILITIES)));
402
+ Marshal.StructureToPtr(secCaps, secCapsPtr, false);
403
+ if (!UpdateProcThreadAttribute(attrList, 0, (IntPtr)PROC_THREAD_ATTRIBUTE_SECURITY_CAPABILITIES,
404
+ secCapsPtr, (UIntPtr)Marshal.SizeOf(typeof(SECURITY_CAPABILITIES)), IntPtr.Zero, IntPtr.Zero))
405
+ {
406
+ outcome.Win32Err = Marshal.GetLastWin32Error();
407
+ DeleteProcThreadAttributeList(attrList);
408
+ Marshal.FreeHGlobal(secCapsPtr); Marshal.FreeHGlobal(attrList); Marshal.FreeHGlobal(sidPtr);
409
+ return outcome;
410
+ }
411
+
412
+ var comspec = Path.Combine(Environment.SystemDirectory, "cmd.exe");
413
+ var cmdline = "\"" + comspec + "\" /d /s /c \"" + command + "\"";
414
+ var siex = new STARTUPINFOEXW();
415
+ siex.StartupInfo.cb = (uint)Marshal.SizeOf(typeof(STARTUPINFOEXW));
416
+ siex.lpAttributeList = attrList;
417
+ siex.StartupInfo.dwFlags = STARTF_USESTDHANDLES;
418
+ siex.StartupInfo.hStdInput = GetStdHandleChecked(-10);
419
+ siex.StartupInfo.hStdOutput = GetStdHandleChecked(-11);
420
+ siex.StartupInfo.hStdError = GetStdHandleChecked(-12);
421
+
422
+ PROCESS_INFORMATION pi;
423
+ if (!CreateProcessW(comspec, cmdline, IntPtr.Zero, IntPtr.Zero, true,
424
+ EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, workspace, siex, out pi))
425
+ {
426
+ outcome.Win32Err = Marshal.GetLastWin32Error();
427
+ DeleteProcThreadAttributeList(attrList);
428
+ Marshal.FreeHGlobal(secCapsPtr); Marshal.FreeHGlobal(attrList); Marshal.FreeHGlobal(sidPtr);
429
+ return outcome;
430
+ }
431
+ AssignProcessToJobObject(jobHandle, pi.hProcess);
432
+
433
+ if (WaitForSingleObject(pi.hProcess, 0xFFFFFFFF) != 0)
434
+ {
435
+ outcome.Win32Err = Marshal.GetLastWin32Error();
436
+ DeleteProcThreadAttributeList(attrList);
437
+ Marshal.FreeHGlobal(secCapsPtr); Marshal.FreeHGlobal(attrList); Marshal.FreeHGlobal(sidPtr);
438
+ return outcome;
439
+ }
440
+ uint exitCode;
441
+ if (!GetExitCodeProcess(pi.hProcess, out exitCode)) outcome.Win32Err = Marshal.GetLastWin32Error();
442
+ else outcome.ChildExit = unchecked((int)exitCode);
443
+ outcome.Started = true;
444
+ CloseHandle(pi.hThread);
445
+ CloseHandle(pi.hProcess);
446
+ DeleteProcThreadAttributeList(attrList);
447
+ Marshal.FreeHGlobal(secCapsPtr); Marshal.FreeHGlobal(attrList); Marshal.FreeHGlobal(sidPtr);
448
+ return outcome;
449
+ }
450
+
451
+ static IntPtr jobHandle = IntPtr.Zero; // exec 主流程的 kill-on-close Job
452
+ static IntPtr GetStdHandleChecked(int which)
453
+ {
454
+ var h = GetStdHandle(which);
455
+ if (h != IntPtr.Zero && h != new IntPtr(-1)) SetHandleInformation(h, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
456
+ return h;
457
+ }
458
+
459
+ static string SidToString(IntPtr sid)
460
+ {
461
+ IntPtr stringSid;
462
+ if (ConvertSidToStringSidW(sid, out stringSid) == 0) return null;
463
+ var text = Marshal.PtrToStringUni(stringSid);
464
+ LocalFree(stringSid);
465
+ return text;
466
+ }
467
+
468
+ // ══ Region E: 动词分发 ═════════════════════════════════════════════
469
+
470
+ static int Main(string[] args)
471
+ {
472
+ for (int i = 0; i < args.Length; i++)
473
+ {
474
+ if (args[i] == "--print-sid") return VerbPrintSid();
475
+ if (args[i] == "--probe") return VerbProbe();
476
+ if (args[i] == "--register") return VerbRegister(args, i + 1 < args.Length && args[i + 1] == "--sig" ? IntArg(args, i + 2) : 0);
477
+ if (args[i] == "--unregister") return VerbUnregister(args, i + 1 < args.Length && args[i + 1] == "--sig" ? IntArg(args, i + 2) : 0);
478
+ }
479
+ return VerbExec(args);
480
+ }
481
+
482
+ static int IntArg(string[] args, int index)
483
+ {
484
+ if (index >= args.Length) return 0;
485
+ int v; return int.TryParse(args[index], out v) ? v : 0;
486
+ }
487
+
488
+ /// --print-sid:协议冻结(Node 侧 launcherSid 依赖 stdout 单行 SID)。
489
+ static int VerbPrintSid()
490
+ {
491
+ var backend = ResolveBackend();
492
+ if (backend.Era == null) Fail("no-backend", "no AppContainer SID derivation export on this OS (tried apiset×N, classic, moniker)");
493
+ IntPtr sid;
494
+ var hr = CallDerive(backend, out sid);
495
+ if (hr != 0) Fail("derive-failed", "derive via " + backend.DeriveName + "(" + backend.ModuleName + ") failed 0x" + hr.ToString("X8"));
496
+ var text = SidToString(sid);
497
+ LocalFree(sid);
498
+ if (text == null) Fail("sid-tostring-failed", "ConvertSidToStringSidW failed");
499
+ Console.Out.WriteLine(text);
500
+ return 0;
501
+ }
502
+
503
+ /// --register / --unregister:hunt 协议专用。逐候选绑定调用并打印 hr;
504
+ /// 错误候选可能使本进程崩溃(可接受——探针边界内,换 --sig 重试)。
505
+ static int VerbRegister(string[] args, int sig)
506
+ {
507
+ var backend = ResolveBackend();
508
+ if (backend.Era == null || backend.Register == IntPtr.Zero) Fail("no-backend", "no register export (era=" + (backend.Era ?? "none") + ")");
509
+ IntPtr sid;
510
+ var hr = CallDerive(backend, out sid);
511
+ if (hr != 0) Fail("derive-failed", "derive failed 0x" + hr.ToString("X8"));
512
+ long result;
513
+ if (sig == 0) result = ((RegisterSig0)Marshal.GetDelegateForFunctionPointer(backend.Register, typeof(RegisterSig0)))(sid, CONTAINER_NAME);
514
+ else if (sig == 1) result = ((RegisterSig1)Marshal.GetDelegateForFunctionPointer(backend.Register, typeof(RegisterSig1)))(CONTAINER_NAME, sid);
515
+ else if (sig == 2) result = ((RegisterSig2)Marshal.GetDelegateForFunctionPointer(backend.Register, typeof(RegisterSig2)))(CONTAINER_NAME, sid, 0);
516
+ else result = ((RegisterSig3)Marshal.GetDelegateForFunctionPointer(backend.Register, typeof(RegisterSig3)))(sid, CONTAINER_NAME, "DSH perm-gate AppContainer");
517
+ Console.Out.WriteLine("register sig=" + sig + " hr=0x" + result.ToString("X8"));
518
+ Console.Out.WriteLine("sid=" + SidToString(sid));
519
+ return 0;
520
+ }
521
+
522
+ static int VerbUnregister(string[] args, int sig)
523
+ {
524
+ var backend = ResolveBackend();
525
+ if (backend.Era == null || backend.Unregister == IntPtr.Zero) Fail("no-backend", "no unregister export (era=" + (backend.Era ?? "none") + ")");
526
+ IntPtr sid;
527
+ var hr = CallDerive(backend, out sid);
528
+ if (hr != 0) Fail("derive-failed", "derive failed 0x" + hr.ToString("X8"));
529
+ long result;
530
+ if (sig == 0) result = ((UnregisterSig0)Marshal.GetDelegateForFunctionPointer(backend.Unregister, typeof(UnregisterSig0)))(sid);
531
+ else if (sig == 1) result = ((UnregisterSig1)Marshal.GetDelegateForFunctionPointer(backend.Unregister, typeof(UnregisterSig1)))(CONTAINER_NAME);
532
+ else result = ((UnregisterSig2)Marshal.GetDelegateForFunctionPointer(backend.Unregister, typeof(UnregisterSig2)))(sid, CONTAINER_NAME);
533
+ Console.Out.WriteLine("unregister sig=" + sig + " hr=0x" + result.ToString("X8"));
534
+ return 0;
535
+ }
536
+
537
+ /// --probe:单行 JSON 裁决,恒 exit 0。tryLaunch 复用完整启动路径
538
+ /// (一次性工作区 + 哨兵命令 exit 42),区分 CreateProcessW 层错误与
539
+ /// 子进程真实退出码。
540
+ static int VerbProbe()
541
+ {
542
+ var probe = new Dictionary<string, string>();
543
+ var backend = ResolveBackend();
544
+ var era = backend.Era ?? "none";
545
+ var deriveHr = "0x0";
546
+ var sidText = "";
547
+ var subAuthorities = 0;
548
+ var lookupState = "n/a";
549
+ var lookupHr = "0x0";
550
+ var capabilities = new Dictionary<string, bool>();
551
+ capabilities["lookup"] = backend.Lookup != IntPtr.Zero;
552
+ capabilities["register"] = backend.Register != IntPtr.Zero;
553
+ capabilities["unregister"] = backend.Unregister != IntPtr.Zero;
554
+ capabilities["createToken"] = backend.CreateToken != IntPtr.Zero;
555
+ var win32Err = 0;
556
+ var childExit = -1;
557
+ var attempted = false;
558
+
559
+ if (backend.Era == null)
560
+ {
561
+ deriveHr = "0xFFFFFFFF";
562
+ }
563
+ else
564
+ {
565
+ IntPtr sid;
566
+ var hr = CallDerive(backend, out sid);
567
+ deriveHr = "0x" + hr.ToString("X8");
568
+ if (hr == 0)
569
+ {
570
+ sidText = SidToString(sid) ?? "";
571
+ if (sidText != "")
572
+ {
573
+ var raw = new byte[new SecurityIdentifier(sidText).BinaryLength];
574
+ new SecurityIdentifier(sidText).GetBinaryForm(raw, 0);
575
+ subAuthorities = raw[1];
576
+ }
577
+ // LookupMoniker / CreateAppContainerToken 的签名均未公开确认
578
+ // (LookupMoniker 错误形状已在实测中 AV 掉探针进程)——v1 只
579
+ // 报告导出存在性,不做调用;确认签名前「注册态」恒 n/a,
580
+ // tryLaunch 的 win32Err 是唯一行为信号。hunt 见 --register。
581
+ // tryLaunch:完整路径 + 哨兵命令。探针工作区独立、只读模式。
582
+ var probeWs = Path.Combine(Path.GetTempPath(), "dsh-pg-ac-probe-" + Guid.NewGuid().ToString("N"));
583
+ attempted = true;
584
+ var outcome = TryLaunch(new SecurityIdentifier(sidText), probeWs, probeWs, "read-only", null, "exit 42");
585
+ win32Err = outcome.Win32Err;
586
+ childExit = outcome.ChildExit;
587
+ try { Directory.Delete(probeWs, true); } catch (Exception) { }
588
+ }
589
+ }
590
+
591
+ // Environment.OSVersion 在 .NET Framework 下按兼容清单撒谎(本机报
592
+ // 9200/Win8)——真 build 从注册表读(CurrentBuild + UBR)。
593
+ var osBuild = ReadRealOsBuild();
594
+ var sb = new System.Text.StringBuilder();
595
+ sb.Append("{");
596
+ sb.Append("\"era\":\"" + era + "\",");
597
+ sb.Append("\"derive\":{\"name\":\"" + backend.DeriveName + "\",\"module\":\"" + backend.ModuleName + "\",\"hr\":\"" + deriveHr + "\"},");
598
+ sb.Append("\"sid\":\"" + sidText + "\",");
599
+ sb.Append("\"sidSubAuthorities\":" + subAuthorities + ",");
600
+ sb.Append("\"registered\":{\"state\":\"" + lookupState + "\",\"lookupHr\":\"" + lookupHr + "\",\"mappingMoniker\":null},");
601
+ sb.Append("\"tryLaunch\":{\"attempted\":" + (attempted ? "true" : "false") + ",\"win32Err\":" + win32Err + ",\"childExit\":" + (childExit == -1 ? "null" : childExit.ToString()) + "},");
602
+ sb.Append("\"osBuild\":" + osBuild + ",");
603
+ sb.Append("\"capabilities\":{");
604
+ var first = true;
605
+ foreach (KeyValuePair<string, bool> kv in capabilities)
606
+ {
607
+ if (!first) sb.Append(",");
608
+ first = false;
609
+ sb.Append("\"" + kv.Key + "\":" + (kv.Value ? "true" : "false"));
610
+ }
611
+ sb.Append("},");
612
+ sb.Append("\"mxc\":{\"present\":" + (File.Exists(Path.Combine(Environment.SystemDirectory, "processmodel.dll")) ? "true" : "false") + "}");
613
+ sb.Append("}");
614
+ Console.Out.WriteLine(sb.ToString());
615
+ return 0;
616
+ }
617
+
618
+ /// --config + --exec-b64:执行主路径(契约不变;config 的 allowRegister
619
+ /// 字段为 hunt 定案前的预留位,本版本恒不读)。
620
+ static int VerbExec(string[] args)
621
+ {
622
+ string configPath = null;
623
+ string encoded = null;
624
+ for (int i = 0; i < args.Length; i++)
625
+ {
626
+ if (args[i] == "--config" && i + 1 < args.Length) { configPath = args[i + 1]; i++; }
627
+ else if (args[i] == "--exec-b64" && i + 1 < args.Length) { encoded = args[i + 1]; i++; }
628
+ }
629
+ if (configPath == null || encoded == null) Fail("usage", "usage: launcher [--print-sid | --probe | --register] | --config <file> --exec-b64 <base64>");
630
+
631
+ IDictionary<string, object> cfg;
632
+ try
633
+ {
634
+ var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
635
+ cfg = serializer.Deserialize<IDictionary<string, object>>(File.ReadAllText(configPath));
636
+ }
637
+ catch (Exception e) { Fail("config-read", "cannot read launch config: " + e.Message); return EXIT_LAUNCH_FAILURE; }
638
+
639
+ var workspace = AsString(cfg, "workspace");
640
+ var tempDir = AsString(cfg, "tempDir");
641
+ var mode = AsString(cfg, "mode");
642
+ if (workspace == null || tempDir == null) Fail("config-shape", "launch config missing workspace/tempDir");
643
+
644
+ var backend = ResolveBackend();
645
+ if (backend.Era == null) Fail("no-backend", "no AppContainer SID derivation export on this OS (tried apiset×N, classic, moniker)");
646
+ IntPtr sid;
647
+ var hr = CallDerive(backend, out sid);
648
+ if (hr != 0) Fail("derive-failed", "derive via " + backend.DeriveName + "(" + backend.ModuleName + ") failed 0x" + hr.ToString("X8"));
649
+ var sidText = SidToString(sid);
650
+ if (sidText == null) Fail("sid-tostring-failed", "ConvertSidToStringSidW failed");
651
+ SecurityIdentifier containerSid;
652
+ try { containerSid = new SecurityIdentifier(sidText); }
653
+ catch (Exception e) { Fail("sid-parse", "bad container SID: " + e.Message); return EXIT_LAUNCH_FAILURE; }
654
+
655
+ string command;
656
+ try { command = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(encoded)); }
657
+ catch (Exception e) { Fail("payload", "bad --exec-b64 payload: " + e.Message); return EXIT_LAUNCH_FAILURE; }
658
+
659
+ // kill-on-close Job:启动器死 → 子进程树陪葬。
660
+ jobHandle = CreateJobObjectW(IntPtr.Zero, null);
661
+ if (jobHandle == IntPtr.Zero) Fail("job-create", "CreateJobObjectW failed (win32 " + Marshal.GetLastWin32Error() + ")");
662
+ var limit = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
663
+ limit.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
664
+ var limitPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)));
665
+ Marshal.StructureToPtr(limit, limitPtr, false);
666
+ if (!SetInformationJobObject(jobHandle, JobObjectExtendedLimitInformation, limitPtr, (uint)Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION))))
667
+ Fail("job-config", "SetInformationJobObject failed (win32 " + Marshal.GetLastWin32Error() + ")");
668
+
669
+ var proxyEnv = new Dictionary<string, string>();
670
+ var rawEnv = cfg["proxyEnv"] as IDictionary<string, object>;
671
+ if (rawEnv != null)
672
+ {
673
+ foreach (KeyValuePair<string, object> kv in rawEnv)
674
+ proxyEnv[kv.Key] = kv.Value == null ? null : kv.Value.ToString();
675
+ }
676
+
677
+ var outcome = TryLaunch(containerSid, workspace, tempDir, mode, proxyEnv, command);
678
+ if (!outcome.Started)
679
+ Fail("launch-failed", "CreateProcessW(SECURITY_CAPABILITIES) failed (win32 " + outcome.Win32Err + ")");
680
+ CloseHandle(jobHandle);
681
+ return outcome.ChildExit;
682
+ }
683
+
684
+ static string AsString(IDictionary<string, object> cfg, string key)
685
+ {
686
+ object v;
687
+ return cfg.TryGetValue(key, out v) && v != null ? v.ToString() : null;
688
+ }
689
+
690
+ /// Real OS build number(HKLM CurrentBuild;Environment.OSVersion 会按
691
+ /// 兼容清单返回假值——本机实测报 9200)。
692
+ static int ReadRealOsBuild()
693
+ {
694
+ try
695
+ {
696
+ var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows NT\CurrentVersion");
697
+ if (key == null) return Environment.OSVersion.Version.Build;
698
+ return Convert.ToInt32(key.GetValue("CurrentBuild", Environment.OSVersion.Version.Build));
699
+ }
700
+ catch (Exception) { return Environment.OSVersion.Version.Build; }
701
+ }
702
+ }
703
+ }