u-chan-secrets 3.0.3 → 4.0.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.
@@ -0,0 +1,137 @@
1
+ // Disposable fixture only. Records F24 canary counts, never ordinary keystrokes or images.
2
+ using System;
3
+ using System.IO;
4
+ using System.IO.Pipes;
5
+ using System.Drawing;
6
+ using System.Diagnostics;
7
+ using System.Collections.Generic;
8
+ using System.Runtime.InteropServices;
9
+ using System.Security.Principal;
10
+ using System.Threading;
11
+ using System.Windows.Forms;
12
+
13
+ public static class UChanDesktopAttacker
14
+ {
15
+ private delegate IntPtr Hook(int code, IntPtr message, IntPtr data);
16
+ private static Hook callback = ObserveCanary;
17
+ private static int canaries;
18
+ [DllImport("user32.dll", SetLastError=true)] private static extern IntPtr SetWindowsHookEx(int kind, Hook hook, IntPtr module, uint thread);
19
+ [DllImport("user32.dll")] private static extern bool UnhookWindowsHookEx(IntPtr hook);
20
+ [DllImport("user32.dll")] private static extern IntPtr CallNextHookEx(IntPtr hook, int code, IntPtr message, IntPtr data);
21
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode)] private static extern IntPtr GetModuleHandle(string name);
22
+ [DllImport("user32.dll", SetLastError=true)] private static extern uint SendInput(uint count, INPUT[] inputs, int size);
23
+ [DllImport("user32.dll", SetLastError=true)] private static extern bool PostMessage(IntPtr window, uint message, IntPtr wParam, IntPtr lParam);
24
+ [DllImport("user32.dll", SetLastError=true)] private static extern bool PrintWindow(IntPtr window, IntPtr dc, uint flags);
25
+ [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr window,int command);
26
+ [DllImport("user32.dll", CharSet=CharSet.Unicode, SetLastError=true)] private static extern IntPtr OpenDesktop(string name, uint flags, bool inherit, uint access);
27
+ [DllImport("user32.dll")] private static extern bool CloseDesktop(IntPtr desktop);
28
+ [StructLayout(LayoutKind.Sequential)] private struct MOUSE { public int x,y; public uint data,flags,time; public IntPtr extra; }
29
+ [StructLayout(LayoutKind.Sequential)] private struct KEY { public ushort key,scan; public uint flags,time; public IntPtr extra; }
30
+ [StructLayout(LayoutKind.Explicit)] private struct UNION { [FieldOffset(0)] public MOUSE mouse; [FieldOffset(0)] public KEY keyboard; }
31
+ [StructLayout(LayoutKind.Sequential)] private struct INPUT { public uint kind; public UNION data; }
32
+
33
+ private static IntPtr ObserveCanary(int code, IntPtr message, IntPtr data)
34
+ {
35
+ if(code >= 0 && Marshal.ReadInt32(data) == 0x87) canaries++;
36
+ return CallNextHookEx(IntPtr.Zero,code,message,data);
37
+ }
38
+ private static uint Key(ushort key)
39
+ {
40
+ INPUT[] values = new INPUT[2];
41
+ values[0].kind=1; values[0].data.keyboard.key=key;
42
+ values[1].kind=1; values[1].data.keyboard.key=key; values[1].data.keyboard.flags=2;
43
+ return SendInput(2,values,Marshal.SizeOf(typeof(INPUT)));
44
+ }
45
+ private static void Pump(int milliseconds)
46
+ {
47
+ Stopwatch watch=Stopwatch.StartNew();
48
+ while(watch.ElapsedMilliseconds < milliseconds){ Application.DoEvents(); Thread.Sleep(5); }
49
+ }
50
+ private static int Markers(Bitmap image)
51
+ {
52
+ int count=0;
53
+ for(int x=0;x<image.Width;x+=4) for(int y=0;y<image.Height;y+=4)
54
+ { Color c=image.GetPixel(x,y); if(c.R==1 && c.G==254 && c.B==71) count++; }
55
+ return count;
56
+ }
57
+ private static bool RenderMarker(IntPtr window, int width, int height)
58
+ {
59
+ using(Bitmap bitmap=new Bitmap(width,height)) using(Graphics graphics=Graphics.FromImage(bitmap))
60
+ {
61
+ IntPtr dc=graphics.GetHdc();
62
+ try { PrintWindow(window,dc,0); } finally { graphics.ReleaseHdc(dc); }
63
+ return Markers(bitmap)>32;
64
+ }
65
+ }
66
+ private static bool DeniedDesktop(string name,uint access)
67
+ {
68
+ IntPtr desktop=OpenDesktop(name,0,false,access);
69
+ if(desktop!=IntPtr.Zero){CloseDesktop(desktop);return false;}
70
+ return Marshal.GetLastWin32Error()==5;
71
+ }
72
+ private static void Put(List<string> results,string key,object value){results.Add(key+"="+value);}
73
+
74
+ [STAThread] public static int Main(string[] args)
75
+ {
76
+ List<string> results=new List<string>();
77
+ IntPtr hook=IntPtr.Zero;
78
+ try
79
+ {
80
+ bool medium=!WindowsIdentity.GetCurrent().IsSystem && !new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator);
81
+ Put(results,"nonElevated",medium);
82
+ if(!medium)throw new InvalidOperationException("Non-elevated caller required");
83
+ hook=SetWindowsHookEx(13,callback,GetModuleHandle(null),0);
84
+ if(hook==IntPtr.Zero)throw new InvalidOperationException("Hook positive control unavailable");
85
+ bool captureControl;
86
+ using(Form control=new Form())
87
+ {
88
+ control.Text="U-Chan disposable positive control";
89
+ control.ClientSize=new Size(96,96); control.BackColor=Color.FromArgb(1,254,71); control.TopMost=true;
90
+ control.Show(); ShowWindow(control.Handle,5); control.Activate(); control.Update(); Pump(200);
91
+ captureControl=RenderMarker(control.Handle,128,128);
92
+ Key(0x87); Pump(300);
93
+ control.Close();
94
+ }
95
+ Put(results,"hookPositiveControl",canaries>0); Put(results,"capturePositiveControl",captureControl);
96
+ if(canaries==0 || !captureControl)throw new InvalidOperationException("Positive control failed");
97
+ canaries=0;
98
+ File.WriteAllText(args[1]+".armed","ready");
99
+ Stopwatch watch=Stopwatch.StartNew();
100
+ while(!File.Exists(args[0]+".metadata") && watch.ElapsedMilliseconds<120000)Pump(20);
101
+ string[] metadata=File.ReadAllLines(args[0]+".metadata");
102
+ if(metadata.Length!=6 || metadata[4]!=args[2])throw new InvalidOperationException("Stale fixture metadata");
103
+ Put(results,"runId",args[2]);
104
+ Put(results,"desktopReadDenied",DeniedDesktop(metadata[0],1));
105
+ Put(results,"desktopHookDenied",DeniedDesktop(metadata[0],8));
106
+ Put(results,"desktopSwitchDenied",DeniedDesktop(metadata[0],0x100));
107
+ bool pipeDenied=false,spoofDenied=false,connected=false;
108
+ try {using(NamedPipeClientStream pipe=new NamedPipeClientStream(".",metadata[5],PipeDirection.Out)){pipe.Connect(300);connected=true;}}
109
+ catch(UnauthorizedAccessException){pipeDenied=true;}
110
+ catch(IOException e){pipeDenied=(e.HResult&0xffff)==5;Put(results,"replyPipeErrorCode",e.HResult&0xffff);}
111
+ catch(TimeoutException){Put(results,"replyPipeErrorCode","timeout");}
112
+ Put(results,"replyPipeConnectDenied",pipeDenied);
113
+ Put(results,"replyPipeConnected",connected);
114
+ try {using(NamedPipeServerStream pipe=new NamedPipeServerStream(metadata[1],PipeDirection.In,1)){}}
115
+ catch(IOException){spoofDenied=true;} catch(UnauthorizedAccessException){spoofDenied=true;}
116
+ Put(results,"replyPipeSpoofDenied",spoofDenied);
117
+ IntPtr form=new IntPtr(long.Parse(metadata[2])), button=new IntPtr(long.Parse(metadata[3]));
118
+ Put(results,"postClickAccepted",PostMessage(button,0xF5,IntPtr.Zero,IntPtr.Zero));
119
+ Put(results,"sendInputCount",Key(0x0D));
120
+ Put(results,"printWindowSawMarker",RenderMarker(form,500,420));
121
+ bool screenMarker=false;
122
+ try {
123
+ Rectangle bounds=Screen.PrimaryScreen.Bounds;
124
+ using(Bitmap image=new Bitmap(bounds.Width,bounds.Height)) using(Graphics graphics=Graphics.FromImage(image))
125
+ { graphics.CopyFromScreen(bounds.Location,Point.Empty,bounds.Size); screenMarker=Markers(image)>32; }
126
+ } catch(System.ComponentModel.Win32Exception) { }
127
+ Put(results,"screenCaptureSawMarker",screenMarker);
128
+ watch.Restart();
129
+ while(!File.Exists(args[0]+".done") && watch.ElapsedMilliseconds<10000)Pump(20);
130
+ Pump(100); Put(results,"protectedCanaryEvents",canaries);
131
+ }
132
+ catch(Exception e){Put(results,"errorType",e.GetType().Name);Put(results,"errorCode",e.HResult&0xffff);}
133
+ finally {if(hook!=IntPtr.Zero)UnhookWindowsHookEx(hook);}
134
+ File.WriteAllLines(args[1]+".attacker",results);
135
+ return 0;
136
+ }
137
+ }
@@ -0,0 +1,125 @@
1
+ // SYSTEM-only disposable browser fixture. Random credentials stay in this process's memory.
2
+ import fs from 'node:fs/promises'
3
+ import path from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+ import { createServer } from 'node:http'
6
+ import { randomUUID } from 'node:crypto'
7
+ import { execFile, spawn } from 'node:child_process'
8
+ import { promisify } from 'node:util'
9
+ import { moduleAuditSucceeded } from './result-policy.mjs'
10
+ const exec = promisify(execFile), root = path.resolve(process.argv[2])
11
+ const home = path.join(process.env.ProgramData ?? 'C:\\ProgramData', 'u-chan-secrets')
12
+ if (!root.startsWith(path.resolve(home, '#0 backup') + path.sep)) throw new Error('Unsafe fixture path')
13
+ const result = { ok: false }, started = Date.now()
14
+ const load = relative => import(pathToFileURL(path.join(home, 'program', relative)).href)
15
+ const { stdout: identity } = await exec('whoami.exe', ['/user'], { windowsHide: true })
16
+ if (!identity.includes('S-1-5-18')) throw new Error('SYSTEM runner required')
17
+ const username = randomUUID() + '@example.invalid', password = randomUUID() + randomUUID(), cookie = 'u_chan_probe_' + randomUUID().replaceAll('-', '')
18
+ let server, shared, userPage, browser, provider, bitwarden, bwTimer
19
+ let receivedCredentials = false
20
+ try {
21
+ const { chromium } = await load('node_modules/playwright/index.mjs')
22
+ const { IsolatedLogin } = await load('dist/service/isolated-login.js')
23
+ const { CdpBrowserProvider, CdpSessionTarget } = await load('dist/core/browser.js')
24
+ const { privateBrowserPath } = await load('dist/platform/runtime-assets.js')
25
+ const form = '<!doctype html><title>U-Chan disposable security fixture</title><form method="post" action="/login"><input type="email" name="email"><input type="password" name="password"><button type="submit">Login</button></form>'
26
+ server = createServer((req, res) => {
27
+ if (req.method === 'POST') {
28
+ let body = ''; req.on('data', part => { body += part }); req.on('end', () => {
29
+ const fields = new URLSearchParams(body)
30
+ receivedCredentials = fields.get('email') === username && fields.get('password') === password
31
+ res.writeHead(303, { location: '/account', 'set-cookie': cookie + '=fixture; Path=/; HttpOnly; SameSite=Lax' }); res.end()
32
+ }); return
33
+ }
34
+ res.writeHead(200, { 'content-type': 'text/html' })
35
+ res.end(req.url === '/account' && req.headers.cookie?.includes(cookie + '=fixture') ? '<h1 id="signed-in">Disposable signed-in fixture</h1>' : form)
36
+ })
37
+ await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
38
+ const origin = 'http://127.0.0.1:' + server.address().port
39
+ shared = await chromium.connectOverCDP('http://127.0.0.1:9222')
40
+ userPage = await shared.contexts()[0].newPage()
41
+ await userPage.addInitScript(() => { window.__uChanProbeInputCount = 0; document.addEventListener('input', () => { window.__uChanProbeInputCount++ }, true) })
42
+ await userPage.goto(origin)
43
+ await userPage.locator('input[type=email]').fill('positive-control@example.invalid')
44
+ result.initScriptPositiveControl = await userPage.evaluate(() => window.__uChanProbeInputCount > 0)
45
+ await userPage.goto(origin)
46
+ browser = await chromium.launch({ headless: true, executablePath: await privateBrowserPath(home) })
47
+ provider = new CdpBrowserProvider('http://127.0.0.1:9222')
48
+ const target = new CdpSessionTarget(provider)
49
+ const login = new IsolatedLogin({ launch: async () => browser, target, timeoutMs: 15000, hostCheck: url => new URL(url).hostname === '127.0.0.1' })
50
+ const outcome = await login.run({ domain: '127.0.0.1', hosts: ['127.0.0.1'], item: 'disposable', loginUrl: origin + '/login', success: { selector: '#signed-in' } }, async () => ({ username, password }))
51
+ result.confirmedFixtureLogin = outcome.status === 'logged_in' && receivedCredentials
52
+ result.sharedInputEvents = await userPage.evaluate(() => window.__uChanProbeInputCount)
53
+ await userPage.goto(origin + '/account')
54
+ result.transferredFixtureSession = await userPage.locator('#signed-in').isVisible()
55
+ const { CoreService } = await load('dist/core/service.js')
56
+ const { AllowlistStore } = await load('dist/core/allowlist.js')
57
+ const { GrantCoordinator } = await load('dist/service/grants.js')
58
+ const grantPath = path.join(root, 'fixture-grants.json'), store = new AllowlistStore(grantPath)
59
+ const once = { domain: '127.0.0.1', hosts: ['127.0.0.1'], item: 'disposable', loginUrl: origin + '/login', success: { selector: '#signed-in' }, grantId: randomUUID(), remainingUses: 1 }
60
+ await store.upsert(once)
61
+ let credentialReads = 0
62
+ const deps = { vault: { status: async () => 'unlocked', getLogin: async () => { credentialReads++; return { username, password } } }, allowlist: store, approver: { ask: async () => false }, audit: { record: async () => {} }, login, mode: async () => 'easy' }
63
+ const core = new CoreService(deps), attempts = await Promise.all([core.login(once.domain), core.login(once.domain)])
64
+ const restarted = new CoreService({ ...deps, allowlist: new AllowlistStore(grantPath) })
65
+ const replay = await restarted.login(once.domain)
66
+ result.onceGrantConcurrency = attempts.filter(r => r.ok).length === 1 && attempts.filter(r => r.code === 'grant_exhausted').length === 1 && credentialReads === 1 && replay.code === 'grant_exhausted'
67
+ let wall = Date.now(), mono = 0
68
+ const timed = { ...once, grantId: randomUUID(), remainingUses: undefined, expiresAt: new Date(wall + 60000).toISOString() }
69
+ await store.upsert(timed)
70
+ const clock = new GrantCoordinator(store, { now: () => wall, monotonic: () => mono })
71
+ wall -= 5000; mono += 1000
72
+ await clock.run(async () => {})
73
+ result.clockRollbackPersisted = (await new AllowlistStore(grantPath).get(timed.domain)).expiresAt === new Date(0).toISOString()
74
+ await fs.mkdir(path.join(root, 'bw-fixture'))
75
+ bitwarden = spawn(path.join(home, 'bin', 'bw.exe'), ['status'], { windowsHide: true, stdio: 'ignore', env: { ...process.env, BITWARDENCLI_APPDATA_DIR: path.join(root, 'bw-fixture'), BW_NOINTERACTION: 'true' } })
76
+ const bwFinished = new Promise(resolve => bitwarden.once('exit', resolve))
77
+ bwTimer = setTimeout(() => bitwarden.kill(), 15000)
78
+ const body = await fs.readFile(new URL('./module-audit.ps1', import.meta.url), 'utf8')
79
+ const encodedHome = Buffer.from(home, 'utf8').toString('base64')
80
+ const script = '$auditProcessId=' + process.pid + ';$auditServiceHome=[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String(' + JSON.stringify(encodedHome) + '));\n' + body
81
+ const audit = await exec('powershell.exe', ['-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(script, 'utf16le').toString('base64')], { windowsHide: true, timeout: 20000 })
82
+ result.nativeModules = JSON.parse(audit.stdout)
83
+ await bwFinished; clearTimeout(bwTimer)
84
+ let scanned = 0, matches = 0
85
+ for (const file of [path.join(home, 'audit.log'), path.join(home, 'service.log')]) {
86
+ const text = await fs.readFile(file, 'utf8').catch(() => '')
87
+ if (text) { scanned++; if (text.includes(password) || text.includes(username)) matches++ }
88
+ }
89
+ // SYSTEM's USERPROFILE differs; the trusted administrator passes the desktop transcript directory.
90
+ for (const session of process.argv[3] ? [process.argv[3]] : []) {
91
+ for (const file of await fs.readdir(session, { recursive: true }).catch(() => [])) {
92
+ if (!file.endsWith('.jsonl')) continue
93
+ const full = path.join(session, file), stat = await fs.stat(full).catch(() => null)
94
+ if (!stat || stat.mtimeMs < started || stat.size > 100 * 1024 * 1024) continue
95
+ const text = await fs.readFile(full, 'utf8'); scanned++; if (text.includes(password) || text.includes(username)) matches++
96
+ }
97
+ }
98
+ result.testSecretScan = { artifacts: scanned, matches }
99
+ const { NpmPackageVerifier } = await load('dist/service/update/verifier.js')
100
+ const { ServiceUpdater } = await load('dist/service/update/updater.js')
101
+ const verifier = new NpmPackageVerifier(), verified = await verifier.verify('3.0.2')
102
+ result.realRegistryVerification = verified.ok
103
+ if (verified.ok) {
104
+ const updateHome = path.join(root, 'update-fixture'), programDir = path.join(updateHome, 'program')
105
+ await fs.mkdir(path.join(programDir, 'node_modules'), { recursive: true })
106
+ const pkg = JSON.parse(await fs.readFile(path.join(home, 'program', 'package.json'), 'utf8'))
107
+ await fs.writeFile(path.join(programDir, 'package.json'), JSON.stringify({ ...pkg, version: '3.0.1' }))
108
+ await fs.writeFile(path.join(programDir, 'node_modules', 'private-marker'), 'preserve-private-dependencies')
109
+ const state = JSON.stringify({ protectedFixture: randomUUID() })
110
+ await fs.writeFile(path.join(updateHome, 'settings.json'), state)
111
+ let restarts = 0
112
+ const update = new ServiceUpdater({ programDir, stagingDir: path.join(updateHome, 'staging'), currentVersion: '3.0.1', verifier: { checkLatest: async () => ({ isNewer: true, latestVersion: '3.0.2' }), verify: async () => verified }, restarter: { restart: async () => { restarts++ } } })
113
+ const updated = await update.update()
114
+ await new Promise(resolve => setImmediate(resolve))
115
+ result.signedUpdateRehearsal = updated.ok && JSON.parse(await fs.readFile(path.join(programDir, 'package.json'), 'utf8')).version === '3.0.2' && await fs.readFile(path.join(programDir, 'node_modules', 'private-marker'), 'utf8') === 'preserve-private-dependencies' && await fs.readFile(path.join(updateHome, 'settings.json'), 'utf8') === state && restarts === 1
116
+ }
117
+ result.ok = result.initScriptPositiveControl && result.confirmedFixtureLogin && result.sharedInputEvents === 0 && result.transferredFixtureSession && result.onceGrantConcurrency && result.clockRollbackPersisted && moduleAuditSucceeded(result.nativeModules) && result.realRegistryVerification && result.signedUpdateRehearsal && matches === 0
118
+ } catch (e) { result.errorType = e.name }
119
+ finally {
120
+ clearTimeout(bwTimer); if (bitwarden && bitwarden.exitCode === null) bitwarden.kill()
121
+ if (shared) await shared.contexts()[0].clearCookies({ name: cookie }).catch(() => {})
122
+ await userPage?.close().catch(() => {}); await provider?.disconnect().catch(() => {}); await browser?.close().catch(() => {}); await shared?.close().catch(() => {})
123
+ if (server) await new Promise(resolve => { server.close(resolve); server.closeAllConnections() })
124
+ }
125
+ await fs.writeFile(path.join(root, 'browser-result.json'), JSON.stringify(result, null, 2))
@@ -0,0 +1,16 @@
1
+ // SYSTEM-side fixture: emits only the non-text F24 canary, never password data.
2
+ public static class UChanProbeCanary
3
+ {
4
+ [System.Runtime.InteropServices.DllImport("user32.dll",SetLastError=true)] private static extern uint SendInput(uint count, INPUT[] values,int size);
5
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] private struct MOUSE { public int x,y; public uint data,flags,time; public System.IntPtr extra; }
6
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] private struct KEY { public ushort key,scan; public uint flags,time; public System.IntPtr extra; }
7
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Explicit)] private struct UNION { [System.Runtime.InteropServices.FieldOffset(0)] public MOUSE mouse; [System.Runtime.InteropServices.FieldOffset(0)] public KEY keyboard; }
8
+ [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] private struct INPUT { public uint kind; public UNION data; }
9
+ public static uint Send()
10
+ {
11
+ INPUT[] values=new INPUT[2];
12
+ values[0].kind=1;values[0].data.keyboard.key=0x87;
13
+ values[1].kind=1;values[1].data.keyboard.key=0x87;values[1].data.keyboard.flags=2;
14
+ return SendInput(2,values,System.Runtime.InteropServices.Marshal.SizeOf(typeof(INPUT)));
15
+ }
16
+ }
@@ -0,0 +1,36 @@
1
+ # Internal SYSTEM fixture. The caller supplies auditProcessId and auditServiceHome.
2
+ $ErrorActionPreference='Stop'
3
+ Import-Module "$PSHOME\Modules\Microsoft.PowerShell.Security\Microsoft.PowerShell.Security.psd1"
4
+ $all=@(Get-CimInstance Win32_Process)
5
+ $ids=@($auditProcessId)
6
+ for($i=0;$i -lt $ids.Count;$i++) {
7
+ $parent=$ids[$i]
8
+ $ids+=@($all|Where-Object{$_.ParentProcessId -eq $parent}|ForEach-Object{[int]$_.ProcessId})
9
+ }
10
+ $paths=@();$exited=0;$unreadableLive=@()
11
+ foreach($id in $ids) {
12
+ try {$paths+=@((Get-Process -Id $id -ErrorAction Stop).Modules|ForEach-Object{$_.FileName})}
13
+ catch {if(Get-Process -Id $id -ErrorAction SilentlyContinue){$unreadableLive+=$id}else{$exited++}}
14
+ }
15
+ $paths=@($paths|Sort-Object -Unique)
16
+ $servicePrefix=[IO.Path]::GetFullPath($auditServiceHome).TrimEnd('\')+'\'
17
+ $windowsPrefix=[IO.Path]::GetFullPath($env:windir).TrimEnd('\')+'\'
18
+ $outside=@($paths|Where-Object{-not $_.StartsWith($servicePrefix,[StringComparison]::OrdinalIgnoreCase)-and -not $_.StartsWith($windowsPrefix,[StringComparison]::OrdinalIgnoreCase)})
19
+ $defender=0;$unsafe=@()
20
+ foreach($file in $outside) {
21
+ $item=Get-Item -LiteralPath $file -Force
22
+ $signature=Get-AuthenticodeSignature -LiteralPath $file
23
+ $acl=$item.GetAccessControl()
24
+ $owner=$acl.GetOwner([Security.Principal.SecurityIdentifier]).Value
25
+ $trustedOwner=$owner -in @('S-1-5-18','S-1-5-32-544') -or $owner.StartsWith('S-1-5-80-')
26
+ $bad=@($acl.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])|Where-Object{
27
+ $_.AccessControlType -eq 'Allow' -and ($_.FileSystemRights -band 0xD0156) -and $_.IdentityReference.Value -notin @('S-1-5-18','S-1-5-32-544') -and -not $_.IdentityReference.Value.StartsWith('S-1-5-80-')
28
+ })
29
+ if($file.StartsWith('C:\ProgramData\Microsoft\Windows Defender\Platform\',[StringComparison]::OrdinalIgnoreCase)-and -not ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)-and $trustedOwner -and $signature.Status -eq 'Valid' -and $signature.SignerCertificate.Subject -match 'O=Microsoft Corporation' -and $bad.Count -eq 0){$defender++}else{$unsafe+=$file}
30
+ }
31
+ @{
32
+ processes=$ids.Count;modules=$paths.Count;outside=$unsafe;verifiedDefenderModules=$defender
33
+ unreadableLive=$unreadableLive.Count;exitedBeforeInspection=$exited
34
+ bitwardenObserved=($paths -contains (Join-Path $auditServiceHome 'bin\bw.exe'))
35
+ browserObserved=(@($paths|Where-Object{$_.StartsWith((Join-Path $servicePrefix 'bin\browser\'),[StringComparison]::OrdinalIgnoreCase)}).Count -gt 0)
36
+ }|ConvertTo-Json -Compress
@@ -0,0 +1,31 @@
1
+ # Internal admin fixture. Inputs are a fixed ProgramData root and a validated run UUID directory.
2
+ $ErrorActionPreference='Stop'
3
+ $admins=New-Object Security.Principal.SecurityIdentifier('S-1-5-32-544')
4
+ $acl=New-Object Security.AccessControl.DirectorySecurity
5
+ $acl.SetOwner($admins)
6
+ $acl.SetAccessRuleProtection($true,$false)
7
+ foreach($sid in @('S-1-5-18','S-1-5-32-544','S-1-5-32-545')) {
8
+ $rights=if($sid -eq 'S-1-5-32-545'){'ReadAndExecute'}else{'FullControl'}
9
+ $id=New-Object Security.Principal.SecurityIdentifier($sid)
10
+ $acl.AddAccessRule((New-Object Security.AccessControl.FileSystemAccessRule($id,$rights,'ContainerInherit,ObjectInherit','None','Allow')))
11
+ }
12
+ function Assert-ProbeChannel([string]$target) {
13
+ $item=Get-Item -LiteralPath $target -Force
14
+ if(-not $item.PSIsContainer -or ($item.Attributes -band [IO.FileAttributes]::ReparsePoint)){throw 'Public channel is redirected'}
15
+ $current=$item.GetAccessControl()
16
+ if($current.GetOwner([Security.Principal.SecurityIdentifier]).Value -notin @('S-1-5-18','S-1-5-32-544')){throw 'Public channel has untrusted owner'}
17
+ foreach($rule in $current.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) {
18
+ if($rule.AccessControlType -eq 'Allow' -and ($rule.FileSystemRights -band 0xD0156) -and $rule.IdentityReference.Value -notin @('S-1-5-18','S-1-5-32-544')){throw 'Public channel is writable by an untrusted identity'}
19
+ }
20
+ }
21
+ # Parent delete-child access could otherwise override the protected root's DACL.
22
+ $parent=(Get-Item -LiteralPath ([IO.Path]::GetDirectoryName($probePublicRoot)) -Force).GetAccessControl()
23
+ foreach($rule in $parent.GetAccessRules($true,$true,[Security.Principal.SecurityIdentifier])) {
24
+ if($rule.AccessControlType -eq 'Allow' -and ($rule.FileSystemRights -band 0x40) -and -not ($rule.PropagationFlags -band [Security.AccessControl.PropagationFlags]::InheritOnly) -and $rule.IdentityReference.Value -notin @('S-1-5-18','S-1-5-32-544') -and -not $rule.IdentityReference.Value.StartsWith('S-1-5-80-')){throw 'Public channel parent permits untrusted deletion'}
25
+ }
26
+ if(-not [IO.Directory]::Exists($probePublicRoot)){[IO.Directory]::CreateDirectory($probePublicRoot,$acl)|Out-Null}
27
+ # Validate after creation as CreateDirectory may return an existing directory after a race.
28
+ Assert-ProbeChannel $probePublicRoot
29
+ if([IO.Directory]::Exists($probeRunDirectory)-or [IO.File]::Exists($probeRunDirectory)){throw 'Run UUID has already been used'}
30
+ [IO.Directory]::CreateDirectory($probeRunDirectory,$acl)|Out-Null
31
+ Assert-ProbeChannel $probeRunDirectory
@@ -0,0 +1,2 @@
1
+ export function accepted(native: unknown, attacker: unknown, runId?: string): boolean
2
+ export function moduleAuditSucceeded(audit: unknown): boolean
@@ -0,0 +1,9 @@
1
+ const uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
2
+ export function accepted(native, attacker, runId) {
3
+ if (!uuid.test(runId ?? '') || !native || !attacker) return false
4
+ const expected = ['nonElevated', 'hookPositiveControl', 'capturePositiveControl', 'desktopReadDenied', 'desktopHookDenied', 'desktopSwitchDenied', 'replyPipeConnectDenied', 'replyPipeSpoofDenied']
5
+ return native.ok === true && native.runId === runId && attacker.runId === runId && expected.every(k => attacker[k] === 'True') && attacker.postClickAccepted === 'False' && attacker.replyPipeConnected === 'False' && attacker.printWindowSawMarker === 'False' && attacker.screenCaptureSawMarker === 'False' && attacker.protectedCanaryEvents === '0'
6
+ }
7
+ export function moduleAuditSucceeded(audit) {
8
+ return !!audit && Number.isInteger(audit.modules) && audit.modules > 0 && audit.unreadableLive === 0 && Array.isArray(audit.outside) && audit.outside.length === 0 && audit.bitwardenObserved === true && audit.browserObserved === true
9
+ }